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. 最后总结
- 当看到因为
spec.finalizers
卡住namespace无法删除,有可能会直接手动删除namespace中的finalizers
信息,从而让系统删除namespace。但是要相信spec.finalizers
的设计是有原因的,如果偷懒跳过就错过了问题背后的真正原因。 - 当碰到kubernetes系统没有按预期工作时,当根据系统日志,事件等信息仍无法分析到原因并解决时,直接阅读kubernetes源码不失为一种解决问题的方法。同时在理解kubernetes系统架构基础上阅读源码会有更好的效果。