Z.S.K.'s Records

Kubernetes学习(当使用kubectl apply -f 多个资源时,资源创建的顺序是怎么样的)

前几天一个开发同学跑过来跟我说,在自动发布一个应用到k8s集群中时,会出现发布成功,但是容器中的配置文件还是旧配置文件的情况,让我看看原因.第一时间我觉得不可能,但是一细想,好像是有可能发生, 立马在我脑海中出现一个疑问:

使用了这么多年的k8s, kubectl apply -f <directory>时,目录下多个资源文件的apply顺序到底是什么?

kubectl

等到下一秒的我,又可以很清晰且自信地回答上面的问题, 在之前的使用apply -f <directory>时,出现过几次提示没有namespace的情况,类似如下:

1
2
3
4
5
# ls test
config.yml
deploy.yml
ns.yml
service.yml

如上所示,如果直接使用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

解决的办法也很简单:

  1. 要么,按照依赖的顺序将所有资源放到同一个文件中
  2. 要么,修改资源文件的名,依赖项靠前即可
  3. 使用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
2
3
4
5
6
7
8
9
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- service.yml.j2
- configmap.yml.j2
- deployment.yml.j2

namespace: prod

为了验证kustomize生成yaml文件的顺序,可以使用kustomize build .来查看resources下的资源的顺序是怎样的,build有额外的参数可以控制输出结果,这里没有使用,因为生成的结果太长, 就不贴上来了,直接说结论:

通过测试可以知道, resources下的资源文件不管怎么放,都是一样的顺序且不存在资源依赖问题

也就是说,如果deployment依赖于configmap,则一定是configmap先apply.

那为何kubectl没有解决而kustomize解决了呢? 也是从官方文档看到这么一句话

1
2
3
4
5
6
Resource Creation Ordering
Certain Resource Types may be dependent on other Resource Types being created first. e.g. Namespaced
Resources on the Namespaces, RoleBindings on Roles, CustomResources on the CRDs, etc.

When used with a kustomization.yaml, Apply sorts the Resources by Resource type to ensure Resources
with these dependencies are created in the correct order.

翻译: 简单来说,就是kustomize会解决资源之间的依赖关系.

只能从kustomize build的源码来看实现了

源码分析

这里不展开所有的代码了,从build子命令一步一步按调用顺序,

最终在gvk.go中发现有如下的order

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var orderFirst = []string{
"Namespace",
"ResourceQuota",
"StorageClass",
"CustomResourceDefinition",
"ServiceAccount",
"PodSecurityPolicy",
"Role",
"ClusterRole",
"RoleBinding",
"ClusterRoleBinding",
"ConfigMap",
"Secret",
"Endpoints",
"Service",
"LimitRange",
"PriorityClass",
"PersistentVolume",
"PersistentVolumeClaim",
"Deployment",
"StatefulSet",
"CronJob",
"PodDisruptionBudget",
}
var orderLast = []string{
"MutatingWebhookConfiguration",
"ValidatingWebhookConfiguration",
}
var typeOrders = func() map[string]int {
m := map[string]int{}
for i, n := range orderFirst {
m[n] = -len(orderFirst) + i
}
for i, n := range orderLast {
m[n] = 1 + i
}
return m
}()

这里引用了一个名词,gvk, 可参考这篇文章

简单来说gvk其实就是group、kind、version,是定位一种类型的方式,k8s中使用的资源都会使用到

1
2
3
4
apiVersion: apps/v1  # group version
kind: DaemonSet # kind
metadata:
name: node-exporter

回到上面的代码底下那部分代码,是给资源对象计算资源id的,这个id就是在执行kustomize build .命令时用于控制输出的,相关代码在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (p *LegacyOrderTransformerPlugin) Transform(m resmap.ResMap) (err error) {
resources := make([]*resource.Resource, m.Size())
ids := m.AllIds()
sort.Sort(resmap.IdSlice(ids))
for i, id := range ids {
resources[i], err = m.GetByCurrentId(id)
if err != nil {
return errors.Wrap(err, "expected match for sorting")
}
}
m.Clear()
for _, r := range resources {
m.Append(r)
}
return nil
}

m.AllIds()先解析kustomization.yaml中resource字段中的资源文件, 获取到所有的资源ids

sort.Sort(resmap.IdSlice(ids))对所有ids进行排序

最后将排序后的资源文件内容写回到文件中,这个文件就是最终build的结果

分析到了这里,基本可以排除开发同学反馈的问题.

helm

另一个使用最多的资源生成工具, helm又是什么情况呢?

从helm的源码来看,很直接,直接维护了个资源的优先级,v2版本是集成在tiller中,参考这里

v3版本因为废弃了tiller,代码移到了这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var InstallOrder KindSortOrder = []string{
"Namespace",
"NetworkPolicy",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"PodDisruptionBudget",
"ServiceAccount",
"Secret",
"SecretList",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleList",
"ClusterRoleBinding",
"ClusterRoleBindingList",
"Role",
"RoleList",
"RoleBinding",
"RoleBindingList",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"HorizontalPodAutoscaler",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}

在使用Helm部署时,是以这个列表的顺序进行发布的,这们就不存在资源依赖的问题, 卸载时则使用相反的顺序.

有点意思

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?