天天看點

kubernetes namespace删除失敗分析指北

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. 最後總結

  1. 當看到因為

    spec.finalizers

    卡住namespace無法删除,有可能會直接手動删除namespace中的

    finalizers

    資訊,進而讓系統删除namespace。但是要相信

    spec.finalizers

    的設計是有原因的,如果偷懶跳過就錯過了問題背後的真正原因。
  2. 當碰到kubernetes系統沒有按預期工作時,當根據系統日志,事件等資訊仍無法分析到原因并解決時,直接閱讀kubernetes源碼不失為一種解決問題的方法。同時在了解kubernetes系統架構基礎上閱讀源碼會有更好的效果。

繼續閱讀