基本使用
1 簡單的yaml文件
在K8s集羣上可使用Kubectl命令以指定文件方式創建一個kind=Deployment的資源對象
$ kubectl create -f nginx.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
下圖分別爲 在終端查看生成的DeployMent, ReplicaSet, pod資源,以及他們之前的拓撲關係圖(可以先忽略oldReplicaSet)
如圖,k8s根據yaml中指定的spec.replicas值爲我們創建3個pod,並在deployment整個運行週期中維護這個數量,然後根據spec.template.spec中的container數組配置,將容器組啓動在每個pod中。
這是一個簡單創建deployment任務過程。
2 更新及回滾
Deployment作爲一個大數據結構(yaml文件)控制維護我們的業務,我們通過更新這個yaml文件來更新業務部署。
上節提到spec.template下是具體pod要啓動的業務及配置,只有對spec.template進行更新纔會觸發pod重新部署(橫向擴縮容不觸發重新部署)
命令行支持兩種更新方式,更新後自動觸發deployment更新
更新結果是根據deployment中配置的replicas數和spec.template中定義的模板,生成pod。然後清理上版本的舊pod。
//直接使用 kubectl set 更新對象
$ kubectl set image deployment/nginx nginx=nginx:1.9.1
//直接更新nginx yaml文件
$ kubectl edit deployment/nginx
此時觀察到系統中存在兩個replicaSet,如果正常發佈,Dp擁有兩個Rs,新版Rs下維護3個pod,舊版下0個,k8s默認爲我們保留更新過的版本,方便我們回滾版本使用。
以下是一個更新及回滾過程中Rs的狀態
(初次發佈後Rs狀態 -> set修改鏡像觸發更新 -> 新pod生成舊版本下pod被清理 -> 回滾 -> "舊"版本pod被重建,"新"版本pod被清理)
以上爲基本的更新/回滾流程。兩個問題:
- 過程描述中,回滾後的新舊版本被我加了雙引號
- 倒數第2次get Rs信息,發現版本之間數量的變化並非單獨的清理舊版,發起新版。
(後面讀源碼將講到)
暫停與恢復
暫停態時,對spec.template資源的更新都不會生效。恢復狀態後,再執行更新操作。官方現在給的解釋爲:暫停態爲支持多次更新配置而不用觸發更新。
命令:
$ kubectl rollout pause deployment/nginx //暫停
$ kubectl rollout resume deployment/nginx //恢復
因爲不會觸發更新,所以理論上也不支持回滾。在暫停態時,發起回滾屬於非法操作。
STATUS
Dp結構體主要包含3個部分:
* ObjectMeta 元數據
* DeploymentSpec Dp任務期望狀態
* Status 處理狀態
其中Meta由用戶指定一部分,另一部分系統維護。DpSpec基本由用戶指定,Status完全基本由系統控制,在同步過程中對此狀態進行參考修改。
我目前根據Dp配置中的condition判斷k8s在處理過程中的狀態:
以上表示兩項結果:
- Available 服務是否達到可用狀態(可自定義livenessProbe、readinessProbe等來指定服務可用標誌,默認pod內容器正常啓動即爲可用),圖中此項status爲True,原因爲滿足用戶期望的最小可用實例數
- Progressing 指Dp收到的最近一個更新請求是否完成。例如回滾操作,指定時間內達到用戶預期結果status將置爲true,否則爲False並設置錯誤原因。指定時間由spec.progressDeadlineSecond參數指定,Pause狀態時此值不定義超時。 (此處更新指所有對Dp的更新,包括水平擴容操作)
概念
Label、Seletor and OwnerReference
觀察本文圖1,發現資源名的特點:
創建Dp時,我們定義nginx爲name;Dp生成的Rs名均爲nginx-hashstr;Rs又創建多個pod,pod名爲rs-hashstr
假定Dp->Rs->Pod是一個從上到下的關係,那k8s通過上層selector和下層labels來確認下屬於上,同時下層會保存上層的metaUid信息,用於所屬確認和垃圾回收。
我通過–show-labels 來查看三項資源的lebels信息:
如上,Dp通過 app=roll
來確定Rs,但是不同版本Rs之間必須有差別,所以創建Rs時引入pod-template-hash作爲selector 和 labels,並將其複製給pod.labels,這樣在dp下同時存在兩個版本時,多個Rs可以接管各自的pod
Rs和Pod中,都保存了ownerReferences信息。uid爲所屬Dp.uid。有兩項用處(以獲取dp下擁有的rs爲例):
- 遍歷檢查rs.labels,首先檢查並確認dp.selector需要是它的子集。然後檢查rs.ownerReferences,確認爲Dp信息時,表示此Rs屬於Dp
- 刪除Deployment時,僅操作Dp資源。檢查Rs時,通過確認其uid標識的owner已被刪除,確認是不是清理當前Rs資源
ControllerManager源碼閱讀
簡單介紹一下事件處理前的如何獲取事件集:
爲了減輕對apiserver的壓力,客戶端存在一個Informer,它負責從apiserver端同步發生變更的數據到store,然後從store中讀取需要處理的事件調用相應的Handler。
deploymentController會啓動多個worker去接收store中的deployment-key,Handler處理函數爲syncDeployment
syncDeployment
func (dc *DeploymentController) syncDeployment(key string) error {
//由key值獲取Dp的namespaces和name
namespace, name, err := cache.SplitMetaNamespaceKey(key)
//根據ns、name從系統中獲取deployment當前信息(此時有可能已被delete,在處理同步中會不斷檢查刪除狀態)
deployment, err := dc.dLister.Deployments(namespace).Get(name)
//deepcopy信息,更新狀態時更新拷貝信息然後將副本更新到server
d := deployment.DeepCopy()
//行爲:獲取屬於當前Dp的所有Rs,同時進行一些adopt和release操作
rsList, err := dc.getReplicaSetsForDeployment(d)
//獲取rs列表下的所有pod,返回Map(key爲Rs.UID value爲PodList)
podMap, err := dc.getPodMapForDeployment(d, rsList)
//如果Pod已經被delete,調用getAllReplicaSetsAndSyncRevision更新版本信息,並同步狀態信息。不明白這裏,爲什麼已刪除還要同步狀態
if d.DeletionTimestamp != nil {
return dc.syncStatusOnly(d, rsList)
}
//檢查是否爲暫停或恢復事件。
//暫停時將condition中Progressing中 status=Unknown reason=DpPaused,此時不對其進行處理超時等檢查
//檢查爲恢復請求並且當前爲暫停時,更新Progressing爲 status=Unknown reason=DpResume
if err = dc.checkPausedConditions(d)
//暫停態時,執行sync同步狀態(本節會單獨分析函數)
if d.Spec.Paused {
return dc.sync(d, rsList)
}
//檢查有回滾事件時,回滾版本(下節會分析此函數)
if getRollbackTo(d) != nil {
return dc.rollback(d, rsList)
}
//發現desire與dp.replicas不符時,確定爲正在進行擴縮容事件,調用sync同步
scalingEvent, err := dc.isScalingEvent(d, rsList)
if scalingEvent {
return dc.sync(d, rsList)
}
//根據兩種發佈策略檢查並更新deployment到最新狀態(下節會分析處理函數)
switch d.Spec.Strategy.Type {
case apps.RecreateDeploymentStrategyType:
return dc.rolloutRecreate(d, rsList, podMap)
case apps.RollingUpdateDeploymentStrategyType:
return dc.rolloutRolling(d, rsList)
}
}
getReplicaSetForDeployment
行爲: 輪詢檢查Dp所在ns下所有Rs並返回Dp控制的RsList。
- Dp.Selector與Rs.labels匹配(前者必須爲後者的子集),並且rs.ownerRefrence必須爲Dp的信息,則認爲此Rs屬於Dp,加入RsList
- 如果Rs.owner爲空,並且Dp.Selector匹配Rs.labels, controller將爲Rs.owner添加此Dp的信息,稱爲adopt,並加入RsList
- 如果Rs.Owner爲此Dp信息, 但是selector不匹配,controller將刪除此Rs的Owner信息,稱爲release,此時Rs將稱爲孤兒直到有匹配labels的Dp出現收養它
- 返回RsList
上圖爲Rs.Owner信息。uid爲所屬Dp的元數據,此值全局唯一。
sync函數 在Dp暫停時協調Dp狀態以及擴縮容狀態
- 更新Dp下Rs revision信息 遍歷所有Rs獲取當前最大revision號maxId,檢查maxId的Rs.spec是否等於Dp.spec.template.spec(檢查是否爲newRs的唯一方法),如果存在newRs將其revision更新爲maxId+1,如果不存在,將根據Dp.spec.template.spec創建newRs並設置revision號爲maxid+1(注意:暫停態時不會創建newRs)。更新後將revision和history-revision信息都加入Annotations
- 如果正在進行scale,對activeRs進行scale up/down;如果activeRs不止一個,比如當前正在進行滾動升級,按比例進行擴縮容,計算公式如下:
newRs.replicas / (newRs.replicas+oldRs.replicas) * needScaleNumber
檢查isScale標誌:滾動升級時,rs.replicas可能隨着滾動過程逐次上升,但annotion中有一項" deployment.kubernetes.io/desired-replicas=10"會指定最終的期望狀態,如果此值!=Dp.replicas,則判斷爲isScale狀態 - 如果newRs已經完成更新,將Dp下所有oldRs.replicas調整爲0
- 根據d.spec.RevisionHistoryLimit參數保留最新n個Rs版本,清理其他
- 根據allRs信息計算Dp當前狀態,並更新到server,信息包括:
status := apps.DeploymentStatus{
Replicas: //Dp下所有Rs的期望實例數
UpdatedReplicas: //新版本Rs期望實例數(不論running與否)
ReadyReplicas: //Dp下所有Rs擁有的ReadyPod數
AvailableReplicas: ///Dp下所有Rs擁有的AvaliblePod數
UnavailableReplicas: //Dp下所有Rs擁有的unavailablePod數
}
//更新condition中type=AvaliblePod狀態,下面是avaliblePod個數是否達標時設置的conditon
deploymentutil.NewDeploymentCondition("Available", "True", "MinimumReplicasAvailable", "Deployment has minimum availability.")
deploymentutil.NewDeploymentCondition("Available", "False", "MinimumReplicasAvailable", "Deployment does not have minimum availability")
如上,是被加入queue中的每個deployment被處理的過程,deployment通過更新Rs-yaml信息來同步狀態。
stracy 更新策略
deployment目前支持兩種更新策略:
- Recreate 刪除所有舊pod,然後創建新Pod。一般用於開發環境
- RollingUpdate 滾動更新,刪除一部分oldPod,創建一部分newPod,重複此步驟直到達到Dp預期
RollingUpdate
滾動更新涉及兩個重要參數,配置在deployment.yaml文件中如下:
replicas: 5 #deployment期望實例數
strategy: #升級策略提示符 位於yaml中 .spec下
rollingUpdate:
maxSurge: 1 #更新中允許存在的最大pod數
maxUnavailable: 1 #更新中允許存在的最大不可用pod數, dp.replicas-maxUnava爲最小可用數
type: RollingUpdate #升級策略
更新版本時觸發rollingUpdate,5b69爲新版,85bb爲舊版本。這次更新設置了錯誤的鏡像,所以更新停止在以下狀態:
更新過程:
對5b69版本scaleUp 1個pod
對85bb版本scaleDown 1個pod
對5b69版本scaleUp 1個pod
!對85bb版本scaleDown 1個pod 此時因爲maxUnavaluble限制,此版本不能再縮容,又因爲maxSurge限制,新版本pod不能再發起,由此stuck在上圖狀態
//升級過程中發生的scale up/down,實際上是操作Rs.replicas
func (dc *DeploymentController) rolloutRolling(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
//從rslist中獲取newRs、oldRs,並更新revision信息。參數爲true時,如果不存在newRs就創建它,檢查存在newRs與否的標準是:rs.template = dp.spec.template
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, true)
allRSs := append(oldRSs, newRS)
//調整newRs-yaml信息,通常爲擴容事件。如果操作了rs.replicas, scaledUp=true.
scaledUp, err := dc.reconcileNewReplicaSet(allRSs, newRS, d)
if scaledUp {
return dc.syncRolloutStatus(allRSs, newRS, d)
}
//調整oldRs-yaml信息,通常爲縮容事件。如果操作了rs.replicas,scaledDown=false
scaledDown, err := dc.reconcileOldReplicaSets(allRSs, controller.FilterActiveReplicaSets(oldRSs), newRS, d)
if scaledDown {
return dc.syncRolloutStatus(allRSs, newRS, d)
}
//清理deployment下Rs版本信息
if deploymentutil.DeploymentComplete(d, &d.Status) {
if err := dc.cleanupDeployment(oldRSs, d); err != nil {
return err
}
}
//同步rs狀態
return dc.syncRolloutStatus(allRSs, newRS, d)
}
reconcileNewReplicaSet 檢查newRs.replicas,確定本次滾動新版本需要新建的數目並更新rs-yaml
- 檢查 new.replicas = dp.replicas,等於時返回false,無需更新
- newRs.replicas > dp.replicas時,new版本實例過多,直接scale down至dp.replicas。如果沒達到dp.replicas, 則根據公式計算本次需要scale up的數量,公式如下:
Min ( (maxSurge + dp.replicas - dp.currentPod), dp.replicas - newRs.replicas )
- 檢查同步dpStatus,爲Rs設置Annotations, 添加Event
reconcileOldReplicaSets 檢查oldRs.replicas,確定本次滾動舊版本需要清理的數目並更新rs-yaml
- 獲取所有activeRs下pod總數量,爲0時,返回false,無需更新
- 清理oldRs列表中 UnAvalible狀態的pod,代碼如下:
//最大可清理舊版本pod數量,縮容時,RS下列表pod是經過排序的,保證優先清理Unhealthy Pod
maxCleanCount = allPodsCount - minAvailable - newRSUnavailablePodCount
//遍歷每個ActiveoldRs
totalScaledDown=0 //記錄已清理副本數,不能超過maxCleanCount值
for _, targetRs := range oldRsList {
scaledDownCount := Min(maxCleanCount - totalScaledDown, targetRS.Spec.Replicas-targetRS.Status.AvailableReplicas) //當前Rs縮容數
//向server發送縮容請求
totalScaledDown+=scaledDownCount
}
- 根據配置最小可用數計算本次需要scaleDown的pod,代碼如下:
totalScaleDownCount := availablePodCount - minAvailable
totalScaledDown := int32(0) //記錄本次總scaleDown數目,不能超過totalScaleDownCount值
for _, targetRs := range oldRsList {
scaleDownCount := Min(targetRS.Spec.Replicas, totalScaleDownCount-totalScaledDown)
totalScaledDown += scaleDownCount
}
- 檢查同步dpStatus,爲Rs設置Annotations, 添加Event
以上爲rollingUpdate過程中,deployment-controller通過控制其下rs.replicas值來控制pod更新的過程,簡單流程爲:根據deployment.spec確定newRs和oldRs,通過maxSurge和maxUnavalible限制來不斷添加新版本Pod並刪除舊版本pod,最終達到newRs.replicas=dp.replicas 並且oldRs.replicas=0,標誌progressing正常結束。
Recreate
func (dc *DeploymentController) rolloutRecreate(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
//getRs並更新版本信息,false參數表示如果沒有新版本則不創建
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
if err != nil {
return err
}
allRSs := append(oldRSs, newRS)
activeOldRSs := controller.FilterActiveReplicaSets(oldRSs)
//縮容所有active的舊實例(其下有pod即爲active),如果有縮容操作,則更新狀態
scaledDown, err := dc.scaleDownOldReplicaSetsForRecreate(activeOldRSs, d)
if scaledDown {
return dc.syncRolloutStatus(allRSs, newRS, d)
}
//確認Rs下已經沒有 running/pending/unknown 狀態的pod
if oldPodsRunning(newRS, oldRSs, podMap) {
return dc.syncRolloutStatus(allRSs, newRS, d)
}
//創建newRs並擴容
if newRS == nil {
newRS, oldRSs, err = dc.getAllReplicaSetsAndSyncRevision(d, rsList, true)
allRSs = append(oldRSs, newRS)
}
dc.scaleUpNewReplicaSetForRecreate(newRS, d)
if util.DeploymentComplete(d, &d.Status) {
if err := dc.cleanupDeployment(oldRSs, d); err != nil {
return err
}
}
return dc.syncRolloutStatus(allRSs, newRS, d)
Rollback
func (dc *DeploymentController) rollback(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
//getRs並更新版本號
newRS, allOldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, true)
allRSs := append(allOldRSs, newRS)
rollbackTo := getRollbackTo(d)
if rollbackTo.Revision == 0 {
//如果未指定Revision,遍歷rs找到第二max的revision
rollbackTo.Revision = deploymentutil.LastRevision(allRSs)}
//根據revision遍歷RsList找到對應rs,將rs.spec.template複製給dp.spec.template,並更新此版本爲最新版,deployment-controller將在下次調用getAllReplicaSetsAndSyncRevision時創建newRs
for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs)
if v == rollbackTo.Revision {
performedRollback, err := dc.rollbackToTemplate(d, rs)
return err
}
}
//清理一些anntition相關的信息
return dc.updateDeploymentAndClearRollbackTo(d)
}
如上,回滾相當於一次更新操作,更新dp.spec.template,在同步時,根據此內容生成新的Rs版本,繼而控制產生期望Pod。
syncReplicaSet
func (rsc *ReplicaSetController) syncReplicaSet(key string) error {
startTime := time.Now()
namespace, name, err := cache.SplitMetaNamespaceKey(key)
rs, err := rsc.rsLister.ReplicaSets(namespace).Get(name)
//rs是否需要同步
rsNeedsSync := rsc.expectations.SatisfiedExpectations(key)
//獲取Rs所在ns下所有Pod
//過濾非success和已刪除pod
//獲取ns下所有pod,獲取過程參考Dp獲取Rs
selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
allPods, err := rsc.podLister.Pods(rs.Namespace).List(labels.Everything())
filteredPods := controller.FilterActivePods(allPods)
filteredPods, err = rsc.claimPods(rs, selector, filteredPods)
var manageReplicasErr error
//管理此rs下pod, 增刪pod(具體實現下面講?)
if rsNeedsSync && rs.DeletionTimestamp == nil {
manageReplicasErr = rsc.manageReplicas(filteredPods, rs)
}
//返回rs下關於pod的running/avalidble等狀態數量彙總和status相關的東西
rs = rs.DeepCopy()
newStatus := calculateStatus(rs, filteredPods, manageReplicasErr)
updatedRS, err := updateReplicaSetStatus(rsc.kubeClient.AppsV1().ReplicaSets(rs.Namespace), rs, newStatus)
//狀態還需要同步時,加入隊列
if manageReplicasErr == nil && updatedRS.Spec.MinReadySeconds > 0 &&
updatedRS.Status.ReadyReplicas == *(updatedRS.Spec.Replicas) &&
updatedRS.Status.AvailableReplicas != *(updatedRS.Spec.Replicas) {
rsc.enqueueReplicaSetAfter(updatedRS, time.Duration(updatedRS.Spec.MinReadySeconds)*time.Second)
}
return manageReplicasErr
}
ManageReplicas函數 管理rs下pod,使符合預期
func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *apps.ReplicaSet) error {
diff := len(filteredPods) - int(*(rs.Spec.Replicas))
rsKey, err := controller.KeyFunc(rs)
//avaliblePod數未達到期望值,需要擴容。 burstReplicas爲單次創建Pod限制
if diff < 0 {
diff *= -1
if diff > rsc.burstReplicas {
diff = rsc.burstReplicas
}
//批量創建Pod,批量數字從1開始double增加,這樣可以防止出現相同錯誤的pod大量失敗的情況
//例如,一個嘗試創建大量Pod的低quota的任務將在第一個Pod創建失敗時被停止,返回成功創建數量
//successfulCreations表示成功調用創建pod函數的次數
successfulCreations, err := slowStartBatch(diff, controller.SlowStartInitialBatchSize, func() error {
err := rsc.podControl.CreatePodsWithControllerRef(rs.Namespace, &rs.Spec.Template, rs, metav1.NewControllerRef(rs, rsc.GroupVersionKind))
//重新調起pod數量未達到diff值,這裏應該是一個記錄此次調用失敗,提醒informer下次繼續調用的過程
if skippedPods := diff - successfulCreations; skippedPods > 0 {
for i := 0; i < skippedPods; i++ {
rsc.expectations.CreationObserved(rsKey)
}
}
} else if diff > 0 {
//存在pod超過期望值,getPodsToDelete中對pod按照狀態進行排序,根據數量返回需要刪除pod數組,優先刪除unhealthy等unAvailible態
if diff > rsc.burstReplicas {
diff = rsc.burstReplicas
}
podsToDelete := getPodsToDelete(filteredPods, diff)
for _, pod := range podsToDelete {
go func(targetPod *v1.Pod) {
defer wg.Done()
rsc.podControl.DeletePod(rs.Namespace, targetPod.Name, rs)
}(pod)
}
wg.Wait()
}
}