请选择 进入手机版 | 继续访问电脑版
 找回密码
 立即注册
搜索

K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?

"\u003Cdiv\u003E\u003Cp\u003E阿里云售后技术团队的同学,每天都在处理各式各样千奇百怪的线上问题。常见的有,网络连接失败,服务器宕机,性能不达标,请求响应慢等。但如果要评选,什么问题看起来微不足道事实上却足以让人绞尽脑汁,我相信答案肯定是“删不掉”的问题。比如文件删不掉,进程结束不掉,驱动卸载不了等。\u003C\u002Fp\u003E\u003Cp\u003E这样的问题就像冰山,影藏在它们背后的复杂逻辑,往往超过我们的预想。\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E背景\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E今天我们讨论的这个问题,跟K8S集群的命名空间有关。命名空间是K8S集群资源的“收纳”机制。我们可以把相关的资源,“收纳”到同一个命名空间里,以避免不相关资源之间不必要的影响。\u003C\u002Fp\u003E\u003Cp\u003E命名空间本身也是一种资源。通过集群API Server入口,我们可以新建命名空间,而对于不再使用的命名空间,我们需要清理掉。命名空间的Controller会通过API Server,监视集群中命名空间的变化,然后根据变化来执行预先定义的动作。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F667b437876e94cf6a20edf554eb33914\" img_width=\"1053\" img_height=\"753\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E有时候,我们会遇到下图中的问题,即命名空间的状态被标记成了“Terminating”,但却没有办法被完全删除。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002F29a4a3043e2d474ab7d7921037f5dcf2\" img_width=\"1025\" img_height=\"183\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E从集群入口开始\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E因为删除操作是通过集群API Server来执行的,所以我们要分析API Server的行为。跟大多数集群组件类似,API Server提供了不同级别的日志输出。为了理解API Server的行为,我们将日志级别调整到最高级。然后,通过创建删除tobedeletedb这个命名空间来重现问题。\u003C\u002Fp\u003E\u003Cp\u003E但可惜的是,API Server并没有输出太多和这个问题有关的日志。\u003C\u002Fp\u003E\u003Cp\u003E相关的日志,可以分为两部分。一部分是命名空间被删除的记录,记录显示客户端工具是kubectl,以及发起操作的源IP地址是192.168.0.41,这符合预期;另外一部分是Kube Controller Manager在重复的获取这个命名空间的信息。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002Fc67fb13d75754b11972efebbfdb21e99\" img_width=\"1682\" img_height=\"441\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003EKube Controller Manager实现了集群中大多数的Controller,它在重复获取tobedeletedb的信息,基本上可以判断,是命名空间的Controller在获取这个命名空间的信息。\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003EController在做什么?\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E和上一节类似,我们通过开启Kube Controller Manager最高级别日志,来研究这个组件的行为。在Kube Controller Manager的日志里,可以看到命名空间的Controller在不断地尝试一个失败了的操作,就是清理tobedeletedb这个命名空间里“收纳”的资源。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F1f078ffd43a7401ab43e534f8d8918a5\" img_width=\"1683\" img_height=\"202\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003E怎么样删除“收纳盒”里的资源\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E这里我们需要理解一点,就是命名空间作为资源的“收纳盒”,其实是逻辑意义上的概念。它并不像现实中的收纳工具,可以把小的物件收纳其中。命名空间的“收纳”实际上是一种映射关系。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp9.pstatp.com\u002Flarge\u002Fpgc-image\u002F53765d1674bd431d862538b7537b1da4\" img_width=\"1113\" img_height=\"1203\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E这一点之所以重要,是因为它直接决定了,删除命名空间内部资源的方法。如果是物理意义上的“收纳”,那我们只需要删除“收纳盒”,里边的资源就一并被删除了。而对于逻辑意义上的关系,我们则需要罗列所有资源,并删除那些指向需要删除的命名空间的资源。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003EAPI、Group、Version\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E怎么样罗列集群中的所有资源呢,这个问题需要从集群API的组织方式说起。K8S集群的API不是铁板一块的,它是用分组和版本来组织的。这样做的好处显而易见,就是不同分组的API可以独立的迭代,互不影响。常见的分组如apps,它有v1,v1beta1和v1beta2这三个版本。完整的分组\u002F版本列表,可以使用kubectl api-versions命令看到。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F4894c36fbcda42e289048ba7833bfaef\" img_width=\"1051\" img_height=\"80\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我们创建的每一个资源,都必然属于某一个API分组\u002F版本。以下边Ingress为例,我们指定Ingress资源的分组\u002F版本为networking.k8s.io\u002Fv1beta1。\u003C\u002Fp\u003E\u003Cpre\u003Ekind: Ingress\u003Cbr\u003Emetadata:\u003Cbr\u003E name: test-ingress\u003Cbr\u003Espec:\u003Cbr\u003E rules:\u003Cbr\u003E - http:\u003Cbr\u003E paths:\u003Cbr\u003E - path: \u002Ftestpath\u003Cbr\u003E backend:\u003Cbr\u003E serviceName: test\u003Cbr\u003E servicePort: 80\u003Cbr\u003E\u003C\u002Fpre\u003E\u003Cp\u003E用一个简单的示意图来总结API分组和版本。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F09bb9b4e35a3439baea7e638a45b541f\" img_width=\"2733\" img_height=\"633\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E实际上,集群有很多API分组\u002F版本,每个API分组\u002F版本支持特定的资源类型。我们通过yaml编排资源时,需要指定资源类型kind,以及API分组\u002F版本apiVersion。而要列出资源,我们需要获取API分组\u002F版本的列表。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003EController为什么不能删除命名空间里的资源\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E理解了API分组\u002F版本的概念之后,再回头看Kube Controller Manager的日志,就会豁然开朗。显然命名空间的Controller在尝试获取API分组\u002F版本列表,当遇到metrics.k8s.io\u002Fv1beta1的时候,查询失败了。并且查询失败的原因是“the server is currently unable to handle the request”。\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E再次回到集群入口\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E在上一节中,我们发现Kube Controller Manager在获取metrics.k8s.io\u002Fv1beta1这个API分组\u002F版本的时候失败了。而这个查询请求,显然是发给API Server的。所以我们回到API Server日志,分析metrics.k8s.io\u002Fv1beta1相关的记录。在相同的时间点,我们看到API Server也报了同样的错误“the server is currently unable to handle the request”。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F8b90ab354fe84a56be9fb10243b45ccd\" img_width=\"1567\" img_height=\"86\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E显然这里有一个矛盾,就是API Server明显在正常工作,为什么在获取metrics.k8s.io\u002Fv1beta1这个API分组版本的时候,会返回Server不可用呢?为了回答这个问题,我们需要理解一下API Server的“外挂”机制。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F0f1bc2543cc347dd943bfe5c85da55a3\" img_width=\"1053\" img_height=\"753\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E集群API Server有扩展自己的机制,开发者可以利用这个机制,来实现API Server的“外挂”。这个“外挂”的主要功能,就是实现新的API分组\u002F版本。API Server作为代理,会把相应的API调用,转发给自己的“外挂”。\u003C\u002Fp\u003E\u003Cp\u003E以Metrics Server为例,它实现了metrics.k8s.io\u002Fv1beta1这个API分组\u002F版本。所有针对这个分组\u002F版本的调用,都会被转发到Metrics Server。如下图,Metrics Server的实现,主要用到一个服务和一个pod。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002F859bd274a4fb433f8e2cfc1c54b17689\" img_width=\"1364\" img_height=\"181\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E而上图中最后的apiservice,则是把“外挂”和API Server联系起来的机制。下图可以看到这个apiservice详细定义。它包括API分组\u002F版本,以及实现了Metrics Server的服务名。有了这些信息,API Server就能把针对metrics.k8s.io\u002Fv1beta1的调用,转发给Metrics Server。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp9.pstatp.com\u002Flarge\u002Fpgc-image\u002F1c484b28a63f45aab8a9860581c43f17\" img_width=\"1051\" img_height=\"221\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E节点与Pod之间的通信\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E经过简单的测试,我们发现,这个问题实际上是API server和metrics server pod之间的通信问题。在阿里云K8S集群环境里,API Server使用的是主机网络,即ECS的网络,而Metrics Server使用的是Pod网络。这两者之间的通信,依赖于VPC路由表的转发。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F6f8c4b262c5f48d28c28e6cfce9b3964\" img_width=\"1816\" img_height=\"1116\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E以上图为例,如果API Server运行在Node A上,那它的IP地址就是192.168.0.193。假设Metrics Server的IP是172.16.1.12,那么从API Server到Metrics Server的网络连接,必须要通过VPC路由表第二条路由规则的转发。\u003C\u002Fp\u003E\u003Cp\u003E检查集群VPC路由表,发现指向Metrics Server所在节点的路由表项缺失,所以API server和Metrics Server之间的通信出了问题。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003ERoute Controller为什么不工作?\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E为了维持集群VPC路由表项的正确性,阿里云在Cloud Controller Manager内部实现了Route Controller。这个Controller在时刻监听着集群节点状态,以及VPC路由表状态。当发现路由表项缺失的时候,它会自动把缺失的路由表项填写回去。\u003C\u002Fp\u003E\u003Cp\u003E现在的情况,显然和预期不一致,Route Controller显然没有正常工作。这个可以通过查看Cloud Controller Manager日志来确认。在日志中,我们发现,Route Controller在使用集群VPC id去查找VPC实例的时候,没有办法获取到这个实例的信息。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002Fc14e56a6cc9d484a9b2caa1bcf8fbcb3\" img_width=\"1716\" img_height=\"262\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是集群还在,ECS还在,所以VPC不可能不在了。这一点我们可以通过VPC id在VPC控制台确认。那下边的问题,就是为什么Cloud Controller Manager没有办法获取到这个VPC的信息呢?\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E集群节点访问云资源\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003ECloud Controller Manager获取VPC信息,是通过阿里云开放API来实现的。这基本上等于,从云上一台ECS内部,去获取一个VPC实例的信息,而这需要ECS有足够的权限。目前的常规做法是,给ECS服务器授予RAM角色,同时给对应的RAM角色绑定相应的角色授权。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002F05201d45e370441195212024ac7f0d2f\" img_width=\"1128\" img_height=\"633\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E如果集群组件,以其所在节点的身份,不能获取云资源的信息,那基本上有两种可能性。一是ECS没有绑定正确的RAM角色;二是RAM角色绑定的RAM角色授权没有定义正确的授权规则。检查节点的RAM角色,以及RAM角色所管理的授权,我们发现,针对vpc的授权策略被改掉了。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002F1a15cb8aff1a43a18d2eab4f55d97e6f\" img_width=\"1052\" img_height=\"216\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E当我们把Effect修改成Allow之后,没多久,所有的Terminating状态的namespace全部都消失了。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp9.pstatp.com\u002Flarge\u002Fpgc-image\u002Fbed6126e7f144fbdbc495bae42b15b3c\" img_width=\"979\" img_height=\"423\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E问题大图\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003E总体来说,这个问题与K8S集群的6个组件有关系,分别是API Server及其扩展Metrics Server,Namespace Controller和Route Controller,以及VPC路由表和RAM角色授权。\u003C\u002Fp\u003E\u003Cdiv class=\"pgc-img\"\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002F37d5cfd96fd149a6be74a28f55945529\" img_width=\"1728\" img_height=\"633\" alt=\"K8S从懵圈到熟练 - 我们为什么会删除不了集群的命名空间?\" inline=\"0\"\u003E\u003Cp class=\"pgc-img-caption\"\u003E\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E\u003Cp class=\"ql-align-center\"\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E通过分析前三个组件的行为,我们定位到,集群网络问题导致了API Server无法连接到Metrics Server;通过排查后三个组件,我们发现导致问题的根本原因是VPC路由表被删除且RAM角色授权策略被改动。\u003C\u002Fp\u003E\u003Ch1\u003E\u003Cstrong\u003E后记\u003C\u002Fstrong\u003E\u003C\u002Fh1\u003E\u003Cp\u003EK8S集群命名空间删除不掉的问题,是线上比较常见的一个问题。这个问题看起来无关痛痒,但实际上不仅复杂,而且意味着集群重要功能的缺失。这篇文章全面分析了一个这样的问题,其中的排查方法和原理,希望对大家排查类似问题有一定的帮助。\u003C\u002Fp\u003E\u003Cp\u003E本文作者:shengdong\u003C\u002Fp\u003E\u003C\u002Fdiv\u003E"'.slice(6, -6),      groupId: '6716694593892532743
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册