0x00. 現象說明
在kubernetes叢集中執行
kubectl delete ns {ns-name}
指令來删除
ns-name
後,發現
ns-name
一直停留在Terminating狀态,如下所示:
aliyun$ kubectl get ns
NAME STATUS AGE
ack-system-e2e-test-0 Terminating 14m <-- 停留在Terminating狀态
default Active 5d16h
kube-public Active 5d16h
kube-system Active 5d16h
我們可能會有一個模糊印象,namespace下所有的資源全部删除完成後,系統才能安心删除掉namespace。然後執行
kubectl get all -n {ns-name}
,輸出如下所示:
aliyun$ kubectl get all -n ack-system-e2e-test-0
No resources found.
namespace下明明沒有資源,但是namespace卻一直停留在
terminating
狀态,嗯~跟自己的認知出現偏差~
0x01. 初步分析
既然namespace處在
Terminating
狀态,我們嘗試再次執行删除指令
kubectl delete ns {ns-name}
,輸出如下:
aliyun$ kubectl delete ns ack-system-e2e-test-0
Error from server (Conflict): Operation cannot be fulfilled on namespaces "ack-system-e2e-test-0": The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.
根據錯誤資訊可以看出,當系統确定namespace下所有資源已移除時,namespace會被自動删除。說明namespace下還有資源需要删除或者在确認是否已經全部删除時出現問題。
接下來我們看下namespace的内容(具體如下),可以猜測應該是
spec.finalizers.kubernetes
讓系統一直沒有删除namespace,是以一直停留在
Terminating
狀态。
aliyun$ kubectl get ns ack-system-e2e-test-0 -o yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2019-11-26T23:33:28Z"
deletionTimestamp: "2019-11-26T23:33:43Z"
name: ack-system-e2e-test-0
resourceVersion: "187978124"
selfLink: /api/v1/namespaces/ack-system-e2e-test-0
uid: 260db8c3-10a5-11ea-933e-8656ce263494
spec:
finalizers:
- kubernetes
status:
phase: Terminating
那系統在什麼情況下才能最終删除掉上面的
spec.finalizers.kubernetes
,進而删除namespace呢,有必要分析一下
namespace controller
的源碼實作。
0x02. 源碼探秘
從kubernetes架構可以推測出,删除namespace時系統删除namespace關聯資源的處理應該是在contorller裡面實作的。是以順其自然去分析
namespace controller
的源碼。可以了解如下兩點:
- namespace下所有關聯資源全部删除後,才會删除
。在spec.finalizers
方法中實作。Delete
- 關聯資源删除處理由
方法實作deleteAllContent
具體代碼分析如下:
// k8s.io/kubernetes/pkg/controller/namespace/deletion/namespaced_resources_deleter.go
func (d *namespacedResourcesDeleter) Delete(nsName string) error {
...
// 删除namespace所有關聯資源
estimate, err := d.deleteAllContent(namespace.Name, *namespace.DeletionTimestamp)
...
// 然後删除namespace中的spec.finalizers
namespace, err = d.retryOnConflictError(namespace, d.finalizeNamespace)
...
// 最後删除namespace
if d.deleteNamespaceWhenDone && finalized(namespace) {
return d.deleteNamespace(namespace)
}
return nil
}
func (d *namespacedResourcesDeleter) deleteAllContent(namespace string, namespaceDeletedAt metav1.Time) (int64, error) {
// 擷取叢集中所有注冊namespace scope資源
// discoverResourcesFn = DiscoveryClient.ServerPreferredNamespacedResources
resources, err := d.discoverResourcesFn()
if err != nil {
return estimate, err // 錯誤退出處理1: 擷取失敗錯誤
}
...
// 從namespace scope資源結果中,過濾出所有支援delete的資源
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
groupVersionResources, err := discovery.GroupVersionResources(deletableResources)
if err != nil {
return estimate, err // 錯誤退出2: gvr解析錯誤
}
...
// 在循環中删除namespace下的各個資源
// 支援deletecollection的資源直接調用deletecollection删除,否則list所有該gvr資源然後逐個删除
for gvr := range groupVersionResources {
gvrEstimate, err := d.deleteAllContentForGroupVersionResource(gvr, namespace, namespaceDeletedAt)
...
}
if len(errs) > 0 {
return estimate, utilerrors.NewAggregate(errs) // 錯誤退出3: namespace下gvr資源删除失敗
}
...
}
從源碼分析可以看出,namespace一直處在
Terminating
狀态,肯定是因為下面某種Error給hang住了。
- 錯誤1: 擷取所有注冊namesapce scope資源失敗
- 錯誤2: 擷取資源的gvr資訊解析失敗
- 錯誤3: namespace下某些gvr資源删除失敗
0x03. 困惑解除
針對上面的錯誤,如果有條件分析到
kube-controller-manager
元件的日志,直接去看日志應該很快可以定位到錯誤資訊。我這邊因為網絡原因無法檢視日志,采用逐漸确認錯誤的方案。
- 利用如下指令,擷取所有注冊namespace scope資源(順便提取能delete的資源),輸出如下:
aliyun$ kubectl api-resources --namespaced=true --verbs=delete
helinbodeMacBook-Pro:.kube helinbo$ kubectl api-resources --namespaced=true --verbs=delete
NAME SHORTNAMES APIGROUP NAMESPACED KIND
...
rolebindings rbac.authorization.k8s.io true RoleBinding
roles rbac.authorization.k8s.io true Role
error: unable to retrieve the complete list of server APIs: metrics.k8s.io/v1beta1: the server is currently unable to handle the request
根據輸出資訊,發現擷取
metrics.k8s.io/v1beta1
資源的注冊資訊時出錯了,接着可以執行
kubectl get apiservice
查詢系統中注冊服務,輸出如下(省略了時間資訊):
aliyun$ kubectl get apiservice
helinbodeMacBook-Pro:.kube helinbo$ kubectl get apiservice
NAME SERVICE AVAILABLE
...
v1beta1.events.k8s.io Local True
v1beta1.extensions Local True
v1beta1.metrics.k8s.io kube-system/metrics-server False (MissingEndpoints)
...
根據輸出資訊,發現原來是metrics-server出問題了。當metrics-server恢複正常後(metrics-server元件恢複屬于另一個話題,後續再分享),apiservice等待片刻也恢複正常,同時
Terminating
狀态的namespace也自動被删除了。至此namespace删除失敗問題算真正解決了。
0x04. 最後總結
- 當看到因為
卡住namespace無法删除,有可能會直接手動删除namespace中的spec.finalizers
資訊,進而讓系統删除namespace。但是要相信finalizers
的設計是有原因的,如果偷懶跳過就錯過了問題背後的真正原因。spec.finalizers
- 當碰到kubernetes系統沒有按預期工作時,當根據系統日志,事件等資訊仍無法分析到原因并解決時,直接閱讀kubernetes源碼不失為一種解決問題的方法。同時在了解kubernetes系統架構基礎上閱讀源碼會有更好的效果。