kubernetes -- 删除namespace的过程以及遇到的bug解决

news2024/11/19 22:34:59

通过阅读本篇文章你可以收获如下知识:

  • 解决一个bug。
  • 理解k8s的controller中,删除namespace的源码,理解其中的删除过程。

问题

执行kubectl delete ns {ns-name}命令来删除ns-name的时候,发现状态一直停留在Terminating

[root@k8smaster k8slearn]# kubectl get ns
NAME              STATUS        AGE
default           Active        99m
hello             Terminating   36m
kube-node-lease   Active        99m
kube-public       Active        99m
kube-system       Active        99m

我想到的是可能是namespace底下有资源,等资源被删除之后系统才能安心删除掉namespace,然后我们来看一下资源:

[root@k8smaster k8slearn]# kubectl get all -n hello
No resources found in hello namespace.

发现是没有资源的,那么到底是什么原因让删除失败了呢?

我们看一下namespace的具体内容:

[root@k8smaster k8slearn]# kubectl get ns hello -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2023-02-01T06:42:00Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:status:
        f:phase: {}
    manager: kubectl-create
    operation: Update
    time: "2023-02-01T06:42:00Z"
  name: hello
  resourceVersion: "5676"
  uid: bc48ddf5-7456-44f0-8f7f-597c6a141a0f
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

我猜测问题出在这里:

spec:
  finalizers:
  - kubernetes

那系统在什么情况下才能最终删除掉上面的spec.finalizers.kubernetes,从而删除namespace呢,有必要分析一下namespace controller的源码实现。

源码分析

从kubernetes架构可以推测出,删除namespace时系统删除namespace关联资源的处理应该是在contorller里面实现的。因此顺其自然去分析namespace controller的源码。

这个源码的位置我很快就发现了,因为删除一个命名空间肯定是controller做的事情,所以我们打开controller的这样一个文件,然后很快就发现了namespace,里面有一个deletion,打开之后很容易就发现了。

在这里插入图片描述

我们来看一下删除命名空间的这个源码:

// Delete deletes all resources in the given namespace.
// Before deleting resources:
//   - It ensures that deletion timestamp is set on the
//     namespace (does nothing if deletion timestamp is missing).
//   - Verifies that the namespace is in the "terminating" phase
//     (updates the namespace phase if it is not yet marked terminating)
//
// After deleting the resources:
// * It removes finalizer token from the given namespace.
//
// Returns an error if any of those steps fail.
// Returns ResourcesRemainingError if it deleted some resources but needs
// to wait for them to go away.
// Caller is expected to keep calling this until it succeeds.
func (d *namespacedResourcesDeleter) Delete(nsName string) error {
	// Multiple controllers may edit a namespace during termination
	// first get the latest state of the namespace before proceeding
	// if the namespace was deleted already, don't do anything
	namespace, err := d.nsClient.Get(context.TODO(), nsName, metav1.GetOptions{})
	if err != nil {
		if errors.IsNotFound(err) {
			return nil
		}
		return err
	}
	if namespace.DeletionTimestamp == nil {
		return nil
	}

	klog.V(5).Infof("namespace controller - syncNamespace - namespace: %s, finalizerToken: %s", namespace.Name, d.finalizerToken)

	// ensure that the status is up to date on the namespace
	// if we get a not found error, we assume the namespace is truly gone
	namespace, err = d.retryOnConflictError(namespace, d.updateNamespaceStatusFunc)
	if err != nil {
		if errors.IsNotFound(err) {
			return nil
		}
		return err
	}

	// the latest view of the namespace asserts that namespace is no longer deleting..
	if namespace.DeletionTimestamp.IsZero() {
		return nil
	}

	// return if it is already finalized.
	if finalized(namespace) {
		return nil
	}

	// there may still be content for us to remove
	estimate, err := d.deleteAllContent(namespace)
	if err != nil {
		return err
	}
	if estimate > 0 {
		return &ResourcesRemainingError{estimate}
	}

	// we have removed content, so mark it finalized by us
	_, err = d.retryOnConflictError(namespace, d.finalizeNamespace)
	if err != nil {
		// in normal practice, this should not be possible, but if a deployment is running
		// two controllers to do namespace deletion that share a common finalizer token it's
		// possible that a not found could occur since the other controller would have finished the delete.
		if errors.IsNotFound(err) {
			return nil
		}
		return err
	}
	return nil
}

我们把几行关键的源码分析一下:

	// return if it is already finalized.
	if finalized(namespace) {
		return nil
	}

我们来看一下这个里面发生了什么?

// finalized returns true if the namespace.Spec.Finalizers is an empty list
func finalized(namespace *v1.Namespace) bool {
	return len(namespace.Spec.Finalizers) == 0
}

这个代表如果我的finalized数组为空的话,那么就算作清理完成了,直接退出函数。

所以说到底,这个Finalizers到底是什么呢?我们区kubernetes的官方文档里面查看一下。

以下内容来自于kubernetes的官方文档:

https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/finalizers/

Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后, 再完全删除被标记为删除的资源。 Finalizer 提醒控制器清理被删除的对象拥有的资源。

当你使用清单文件创建资源时,你可以在 metadata.finalizers 字段指定 Finalizers。 当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers 字段中的值, 并进行以下操作:

  • 修改对象,将你开始执行删除的时间添加到metadata.deletionTimestamp字段。
  • 禁止对象被删除,直到其 metadata.finalizers 字段为空。
  • 返回 202 状态码(HTTP “Accepted”)。

管理 finalizer 的控制器注意到对象上发生的更新操作,对象的 metadata.deletionTimestamp 被设置,意味着已经请求删除该对象。然后,控制器会试图满足资源的 Finalizers 的条件。 每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers 字段中删除该键。 当 finalizers 字段为空时,deletionTimestamp 字段被设置的对象会被自动删除。 你也可以使用 Finalizers 来阻止删除未被管理的资源。

一个常见的 Finalizer 的例子是 kubernetes.io/pv-protection, 它用来防止意外删除 PersistentVolume 对象。 当一个 PersistentVolume 对象被 Pod 使用时, Kubernetes 会添加 pv-protection Finalizer。 如果你试图删除 PersistentVolume,它将进入 Terminating 状态, 但是控制器因为该 Finalizer 存在而无法删除该资源。 当 Pod 停止使用 PersistentVolume 时, Kubernetes 清除 pv-protection Finalizer,控制器就会删除该卷。

那么接下来我们就在源码中看一看这个过程:

	estimate, err := d.deleteAllContent(namespace)

首先是删除namespace所有关联资源。

	// we have removed content, so mark it finalized by us
	_, err = d.retryOnConflictError(namespace, d.finalizeNamespace)

然后删除namespace中的spec.finalizers。

而由官方文档得知删除finalizers之后,ns才能被删除。

因此问题就出现在了deleteAllContent这个函数里面,这个函数里面某些东西出现了问题,导致finalizers无法被删除,从而出现上面的情况。

所以我们来读一下deleteAllContent的源码。

// deleteAllContent will use the dynamic client to delete each resource identified in groupVersionResources.
// It returns an estimate of the time remaining before the remaining resources are deleted.
// If estimate > 0, not all resources are guaranteed to be gone.
func (d *namespacedResourcesDeleter) deleteAllContent(ns *v1.Namespace) (int64, error) {
	namespace := ns.Name
	namespaceDeletedAt := *ns.DeletionTimestamp
	var errs []error
	conditionUpdater := namespaceConditionUpdater{}
	estimate := int64(0)
	klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s", namespace)

	resources, err := d.discoverResourcesFn()
	if err != nil {
		// discovery errors are not fatal.  We often have some set of resources we can operate against even if we don't have a complete list
		errs = append(errs, err)
		conditionUpdater.ProcessDiscoverResourcesErr(err)
	}
	// TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter
	deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources)
	groupVersionResources, err := discovery.GroupVersionResources(deletableResources)
	if err != nil {
		// discovery errors are not fatal.  We often have some set of resources we can operate against even if we don't have a complete list
		errs = append(errs, err)
		conditionUpdater.ProcessGroupVersionErr(err)
	}

	numRemainingTotals := allGVRDeletionMetadata{
		gvrToNumRemaining:        map[schema.GroupVersionResource]int{},
		finalizersToNumRemaining: map[string]int{},
	}
	for gvr := range groupVersionResources {
		gvrDeletionMetadata, err := d.deleteAllContentForGroupVersionResource(gvr, namespace, namespaceDeletedAt)
		if err != nil {
			// If there is an error, hold on to it but proceed with all the remaining
			// groupVersionResources.
			errs = append(errs, err)
			conditionUpdater.ProcessDeleteContentErr(err)
		}
		if gvrDeletionMetadata.finalizerEstimateSeconds > estimate {
			estimate = gvrDeletionMetadata.finalizerEstimateSeconds
		}
		if gvrDeletionMetadata.numRemaining > 0 {
			numRemainingTotals.gvrToNumRemaining[gvr] = gvrDeletionMetadata.numRemaining
			for finalizer, numRemaining := range gvrDeletionMetadata.finalizersToNumRemaining {
				if numRemaining == 0 {
					continue
				}
				numRemainingTotals.finalizersToNumRemaining[finalizer] = numRemainingTotals.finalizersToNumRemaining[finalizer] + numRemaining
			}
		}
	}
	conditionUpdater.ProcessContentTotals(numRemainingTotals)

	// we always want to update the conditions because if we have set a condition to "it worked" after it was previously, "it didn't work",
	// we need to reflect that information.  Recall that additional finalizers can be set on namespaces, so this finalizer may clear itself and
	// NOT remove the resource instance.
	if hasChanged := conditionUpdater.Update(ns); hasChanged {
		if _, err = d.nsClient.UpdateStatus(context.TODO(), ns, metav1.UpdateOptions{}); err != nil {
			utilruntime.HandleError(fmt.Errorf("couldn't update status condition for namespace %q: %v", namespace, err))
		}
	}

	// if len(errs)==0, NewAggregate returns nil.
	klog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, estimate: %v, errors: %v", namespace, estimate, utilerrors.NewAggregate(errs))
	return estimate, utilerrors.NewAggregate(errs)
}

我们看有哪些if err != nil。就可以直到哪里会发生错误。

  • 错误1: 获取所有注册namesapce scope资源失败
  • 错误2: 获取资源的gvr信息解析失败
  • 错误3: namespace下某些gvr资源删除失败

那么具体为什么,我们也不知道该怎么解决了,因此有一个治标不治本的方式,就是强行删除finalizers。

解决方法

强制性删除spec.finalizer[]。

  • 查看hello的namespace描述

    kubectl get ns hello -o json > hello.json
    
  • 编辑json文件,删除spec字段的内存,因为k8s集群需要认证

     vim hello.json
    
    "spec": {       
    	"finalizers": [           
        		"kubernetes"
            ]
        },
    更改为:
    "spec": {
        
      },
    
  • 新开一个窗口运行kubectl proxy跑一个API代理在本地的8081端口

    kubectl proxy --port=8081
    
  • 运行curl命令,直接调用kube api进行删除

    curl -k -H "Content-Type:application/json" -X PUT --data-binary @hello.json http://127.0.0.1:8081/api/v1/namespaces/hello/finalize
    

总结

我们来梳理一下删除namespace的过程。

es_user

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/191489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023牛客寒假算法基础集训营5

(0条未读通知) 【题解】2023牛客寒假算法基础集训营5_ICPC/CCPC/NOIP/NOI刷题训练题单_牛客竞赛OJ (nowcoder.com) A-小沙の好客 下面是错误代码,我刚开始看到这题,觉得很简单阿,做了才知道,会超时,所以不能单纯的去做…

Mac电脑运行速度又卡又慢如何解决?CleanMyMacX2023最新版

CleanMyMac X为您喜爱的东西腾出空间。体验一系列巧妙的新功能,CleanMyMac可让您安全智能地扫描和清理整个系统,删除大量未使用的文件,缩小iPhoto图库的大小,卸载不需要的应用程序或修复不正常工作的应用程序,管理所有…

ARM异常处理

1.ARM异常源1.1 异常模式2.ARM异常响应CPSR内存储的是当前模式。2.1 异常向量表每个异常源只有四个字节的存储空间,所以不在向量表中写异常处理程序,而是写跳转指令。2.2 IRQ异常举例

产业互联网曾被认为是一个生搬硬凑出来的概念

有关产业互联网的质疑,依然还在耳畔会响。但,我们却无可避免地进入到了产业互联网的周期里。这听上去有些耸人听闻,却在真实地发生着。曾经,产业互联网被认为是一个生搬硬凑出来的概念,甚至还有人认为,它仅…

垃圾渗滤液膜后稳定出水水质稳定工艺

垃圾渗滤液的来源及水质特点 垃圾渗滤液是垃圾在堆放和填埋过程中由于发酵和雨水的淋洗、冲刷以及地表地下水的浸泡出来的污水。 当垃圾堆体的湿度超过其持水能力后,垃圾堆体内悬浮的或溶解的有机污染物和重金属等无机污染物将会随之一块溶出,便会产生渗…

大学生需要配备什么电子产品、上大学必备电子产品推荐清单

又是一年开学季,最近我们这里的中小学已经陆陆续续通知开学了。大学生正式开学还需要一段时间,作为大学生在入学之前,一定要准备好各种学习和生活物资。对于大学生而言,有大把的时间可以用来快活,而现在数码产品又成为…

中国电子学会2022年06月份青少年软件编程Scratch图形化等级考试试卷四级真题(含答案)

2022-06 Scratch四级真题 分数:100 题数:29 测试时长:100min 一、单选题(共15题,共30分) 1.执行下列程序,说的内容是?(D)(2分) A.使 B.命 C.初 D.心 答案解析:注…

Golang微服务基础技术

单体式和微服务 单体式架构服务 ------过往大家熟悉的服务器 特性: 1.复杂性随着开发越来越高,遇到问题解决困难。 2. 技术债务逐渐上升 3. 耦合度高,维护成本大 出现bug,不容易排查 解决旧bug,会出新bug 4. 持续交付…

“深度学习”学习日记。卷积神经网络--整体结构

2023.2.1 一、全连接层神经网络: 之前学习的神经网络称为 全连接神经网络 ( Fully-Connected),它的特点就是相邻层所有的神经元都有联接,通过Affine层实现全连接层。 在全连接层神经网络中,Affine层后面…

【ROS-Navigation】—— DWA路径规划算法解析

文章目录前言1. 涉及的核心配置文件与启动文件1.1 demo01_gazebo.launch1.2 nav06_path.launch1.3 nav04_amcl.launch1.4 nav05_path.launch1.5 move_base_params.yaml1.6 dwa_local_planner_params.yaml2. 调参时的一些经验与心得2.1 DWA算法流程2.2 对costmap的参数进行调整2…

1. Mybatis基础

文章目录1. Mybatis 简介2. Mybatis 快速入门3. 使用 idea 写 SQL4. Mapper 代理开发5. MyBatis 核心配置文件1. Mybatis 简介 MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发。 官方文档:https://mybatis.org/mybatis-3/zh/index.html 持久层&am…

GoogLeNet——网络实战

文章目录摘要🐇1 项目结构🐇2 划分训练集和测试集🐇3 计算mean和Standard🐇3.1 标准化的作用🐇3.2 归一化的作用🐇4 训练🐇4.1 导入项目使用的库🐇4.2 设置随机因子🐇4.3…

Java单例模式演示与理解

目录单例模式1、饿汉式2、懒汉式3、DSL懒汉式(双重锁懒汉模式)静态内部类懒汉式单例模式的如何破坏4、使用枚举类单例模式 为什么使用单例模式? 单例模式确保一个类在内存中只会有同一个实例对象存在。不管声明获取实例对象多少次都是内存中…

2023年网络安全八大预测!

随着创新技术的不断兴起,以及网络犯罪的日益专业化,网络安全攻击风险仍在持续增长。可以预见,2023年的网络安全形势依然严峻,需要国家不断完善网络安全政策和法规,网络安全企业积极创新网络安全防护技术。瑞数信息作为…

Allegro如何导出和导入器件模型Signal_Model操作指导

Allegro如何导出和导入器件模型Signal_Model操作指导 在用Allegro做PCB设计的时候,通常需要给器件加上Signal_Model,在做等长的时候用到的非常频繁。 Allegro除了可以给器件添加模型,还支持从一块加好模型的BRD导入到另外一块BRD中, 如下图,需要把R7002的Signal_Model导入…

剪报浏览器:可以自己设计网页的浏览器

总的功能就是一句话“不同网站的精华内容裁剪下来,合并到一处浏览”把自己关注的网页版块从不同网站上裁剪下来放在一个页面里,一次刷新就可以看到不同网站的最新内容,而不用逐个打开网站去看,提高了上网的效率。关键特征汇聚浏览…

排序算法(带动图)

0、算法概述0.1 算法分类十种常见排序算法可以分为两大类:比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。非比较类排序:不通过比较来决定元素间的相对…

【数据结构初阶】第七篇——二叉树的顺序结构及实现(堆的向下,向上调整算法)

二叉树的顺序结构 堆的概念及结构 堆的向下调整算法 堆的向上调整算法 堆的实现 初始化堆 销毁堆 打印堆 堆的插入 堆的删除 获取堆顶的数据 获取堆的数据个数 堆的判空 建堆的时间复杂度 二叉树的顺序结构 普通二叉树是不适合用数组来存储的,因为可能会导致大量…

为nginx配置好看的错误提示页面

前言 nginx默认错误页面确实有些丑哈,leeader让我换一个样式 ,我就来喽! 为nginx配置好看的错误提示页面前言1 找异常页原始页2 win上替换3 再linux服务器上替换4 不生效解决办法样式显示不正确6 错误页源码1 找异常页 原始页 nginx默认错误…

2个月快速通过PMP证书的经验

01 PMP证书是什么? 指的是项目管理专业人士资格认证。它是由美国项目管理协会(Project Management Institute(简称PMI))发起的,严格评估项目管理人员知识技能是否具有高品质的资格认证考试。其目的是为了给项目管理人员提供统一的…