前几天一个开发同学跑过来跟我说,在自动发布一个应用到k8s集群中时,会出现发布成功,但是容器中的配置文件还是旧配置文件的情况,让我看看原因.第一时间我觉得不可能,但是一细想,好像是有可能发生, 立马在我脑海中出现一个疑问:
使用了这么多年的k8s, kubectl apply -f <directory>
时,目录下多个资源文件的apply顺序到底是什么?
kubectl
等到下一秒的我,又可以很清晰且自信地回答上面的问题, 在之前的使用apply -f <directory>
时,出现过几次提示没有namespace
的情况,类似如下:
1 | # ls test |
如上所示,如果直接使用kubectl apply -f .
则会出现在创建deploy.yml
时提示找不到namespace
从输出的结果可以得到结论: 在创建多个资源文件时,创建的顺序是以文件名排序的顺序,这样的话就可能存在依赖失败
按道理这个依赖关系对kubernetes来说不是很复杂,为何不支持呢? 个人感觉如果是原生的资源对象,那确实不复杂,但后面引入的CRD
机制,这个依赖关系就不好处理了,所以就是现在这个局面,说不定新版本已经支持.
从官方文档上也有这么一段话:
翻译: apply 也支持指定目录,会在目录下查找yaml、yml、json
为后缀的文件名进行apply
同时,也建议将同一个服务的所有的资源放在一个文件中,这也就是为什么现在很多的服务安装时提示的url都是在一个文件中放置所有的资源.
本人也查找了kubernetes/kubectl
的github,也未发现具体的原因,有如下讨论的issue:
回到开发同学的问题,如果是按照上面的逻辑确实可能存在那个问题, 即
如果apply的资源在集群中已经存在,apply 又是使用的是文件名排序进行patch的,如果deploy中引用的secret,则就有可能在创建deploy时使用了集群中已存在的secret,因为此时新的secret还没有apply
解决的办法也很简单:
- 要么,按照依赖的顺序将所有资源放到同一个文件中
- 要么,修改资源文件的名,依赖项靠前即可
- 使用
kubectl replace --force -f
在创建前先删除
源码分析
从kubectl的源码来看,确实没有看到处理资源依赖相关的代码
这里使用的是v1.15.5版本的kubectl,新版本不太清楚是否已支持依赖资源的创建
kustomize
但是,当前环境使用的是argocd + kustomize
做的CI/CD pipeline,没有直接使用kubectl,发布的时候使用的是kustomize,难道kustomize也不能保证顺序
?
kustomize的版本是v1.15.9
目前使用的kustomizition.yaml文件是统一的,格式如下:
1 | apiVersion: kustomize.config.k8s.io/v1beta1 |
为了验证kustomize生成yaml文件的顺序,可以使用kustomize build .
来查看resources
下的资源的顺序是怎样的,build有额外的参数可以控制输出结果,这里没有使用,因为生成的结果太长, 就不贴上来了,直接说结论:
通过测试可以知道, resources下的资源文件不管怎么放,都是一样的顺序且不存在资源依赖问题
也就是说,如果deployment依赖于configmap,则一定是configmap先apply.
那为何kubectl没有解决而kustomize解决了呢? 也是从官方文档看到这么一句话
1 | Resource Creation Ordering |
翻译: 简单来说,就是kustomize会解决资源之间的依赖关系.
只能从kustomize build的源码来看实现了
源码分析
这里不展开所有的代码了,从build子命令一步一步按调用顺序,
最终在gvk.go中发现有如下的order
1 | var orderFirst = []string{ |
这里引用了一个名词,gvk
, 可参考这篇文章
简单来说gvk
其实就是group、kind、version
,是定位一种类型的方式,k8s中使用的资源都会使用到
1 | apiVersion: apps/v1 # group version |
回到上面的代码底下那部分代码,是给资源对象计算资源id的,这个id就是在执行kustomize build .
命令时用于控制输出的,相关代码在这里
1 | func (p *LegacyOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) { |
m.AllIds()
先解析kustomization.yaml
中resource字段中的资源文件, 获取到所有的资源ids
sort.Sort(resmap.IdSlice(ids))
对所有ids进行排序
最后将排序后的资源文件内容写回到文件中,这个文件就是最终build的结果
分析到了这里,基本可以排除开发同学反馈的问题.
helm
另一个使用最多的资源生成工具, helm又是什么情况呢?
从helm的源码来看,很直接,直接维护了个资源的优先级,v2版本是集成在tiller中,参考这里
v3版本因为废弃了tiller,代码移到了这里
1 | var InstallOrder KindSortOrder = []string{ |
在使用Helm部署时,是以这个列表的顺序进行发布的,这们就不存在资源依赖的问题, 卸载时则使用相反的顺序.
有点意思
参考文章:
- https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/
- https://github.com/kubernetes/kubernetes/issues/24649
- https://github.com/kubernetes/kubernetes/issues/64203
- https://github.com/kubernetes/kubernetes/issues/44511
- https://github.com/helm/helm/blob/release-2.10/pkg/tiller/kind_sorter.go#L29
- https://github.com/helm/helm/blob/master/pkg/releaseutil/kind_sorter.go
- https://liqiang.io/post/kubernetes-all-about-crd-part02-api-server-and-gvkr-dc53e1f1
- https://github.com/kubernetes-sigs/kustomize/blob/master/api/builtins/LegacyOrderTransformer.go