文章目录
1. 函数与包的依赖
2. handler的Image_Policy.go
`review.Status.Reason`是什么?
`v1alpha1.ImageReview`是什么?
`metav1`是表达的什么?
`metav1.ObjectMeta`包含了什么?
`ImageReviewSpec`作为`spect`的类型有什么?
`ImageReviewStatus` 作为`Status`的类型包含了什么?
3. rule目录的not_latest.go
normalize.go
anchoredIdentifierRegexp.MatchString
splitDockerDomain(s)
parse
referenceRegexp.FindStringSubmatch(s)
4. echo包
5. 总结
相关阅读:
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【1】动手实践感受区别所在
KubernetesImagePolicyWebhook与ValidatingAdmissionWebhook【2】Image_Policy.go源码解析
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【3】validating_admission.go源码解析
Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【4】main.go源码解析
kubernetes 快速学习手册
1. 函数与包的依赖
https://golang.google.cn/pkg/regexp/
https://pkg.go.dev/github.com/opencontainers/go-digest#Parse
https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta
https://echo.labstack.com/guide/
https://github.com/containers/image/tree/main/docker/reference
https://github.com/containers/image/blob/main/docker/reference/normalize.go
如果你想 深入了解源码的来龙去脉,是十分有必要打开上面的链接。
对于运维人员来说,如何读懂开发写源代码是很痛苦。往往不知如何去理解才好。没有思路,一个接一个的包的各种接口、方法、函数把你搞的晕头转向。那么我们该如何读懂源码呢?
在上一篇文章Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【1】动手实践感受区别所在,我们熟悉了 ImagePolicyWebhook与ValidatingAdmissionWebhook准入控制的运用,接下来我们去细细的拆分他们的逻辑。
源代码地址:https://github.com/kainlite/kube-image-bouncer
**那么,我们从输出的报错信息出发,逆向推理源代码的逻辑。**也许会是个不错的 原则。
2. handler的Image_Policy.go
我们首先看看ImagePolicyWebhook
检验效果的报错信息
$ kubectl describe rc nginx-latest #警告不允许有latest标签的镜像 Warning FailedCreate 8s (x6 over 87s) replication-controller (combined from similar events): Error creating: pods "nginx-latest-f7667" is forbidden: image policy webhook backend denied one or more images: Images using latest tag are not allowed
搜索关键报错语句:Images using latest tag are not allowed
我们在image_policy.go
的37行找到了
review.Status.Reason是什么?
从代码中:
review.Status.Reason
可能是一个字符串类型,存储我们表达的信息。review.Status
可能是一个接口之类的东西review
是一个v1alpha1.ImageReview
类型的变量
v1alpha1.ImageReview是什么?
从图中我们知道他来自专属k8s.io/api/imagepolicy/v1alpha`的包。那我们只能去它的源代码库查看了。
看到这里我们知道了,v1alpha1.ImageReview
是个结构体,结构体顾名思义是形容一类物体的统称范畴,结构体肯定包含特定属性的组成部分,它即可以抽象的,也可以是具体的。例如:
type 人 struct { 中国人 string 日本人 string 美国人 string ....... }
我们还可以这样分:
type 人 struct { 黄种人 string 黑种人 string 白种人 string ........... } type 人 struct { 名字 string 年龄 int 籍贯 string 人体结构 json 状态 json }
总之,从某一个角度,根据我们是否需要想怎么分就怎么分。
回到本文中来,ImageReview,是描述的什么结构体呢,是一个pod的镜像,有什么内容呢?
metav1.TypeMeta 代表的镜像类型的元信息
metav1.ObjectMeta 代表的镜像对象的元信息
Spec ImageReviewSpec 代表的镜像的细节描述信息
Status ImageReviewStatus 代表的镜像的状态
metav1
是表达的什么?
meta 是标签的意思,v1表达的是版本。
最后发现meta其实也是一个包,它的调用方式是k8s.io/apimachinery/pkg/apis/meta/v1
这个包包含了什么?
包v1包含所有版本都通用的API类型,但它们分为两类,一类是对外的,也就是能为我们开发定制的,另一部分是内部的。总之,我们纵观全览,这个包定义了k8s许多对象。我们继续往下看
metav1.TypeMeta中的TypeMeta包含了什么?
原来,TypeMeta也是结构体,里边包含:
kind: 对象类型,这个我们非常常见,描述资源对象的类型。专业术语描述就是:Kind是一个字符串值,表示该对象所表示的REST资源,不能被更新。
APIversion: APIVersion定义了对象的这种表示的版本模式。
metav1.ObjectMeta包含了什么?
这个不用多说,一查就能知道;由于它的描述非常庞大,这里不展示原文注释,只展示这个结构体有什么,让我们心里有个大概。但陌生的内容我会加点注释。
type ObjectMeta struct { Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` #GenerateName是一个可选的前缀,由服务器使用,以生成惟一的name ONLY如果没有提供name字段。 GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` #SelfLink是一个表示这个对象的URL,Kubernetes将在1.20版本中停止传播该字段,该字段已经计划好了在1.21版本中被删除 SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` UID types.UID `json:"uid,omitempty"protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` #ResourceVersion表示该对象的内部版本的不透明值,客户端可使用该值确定对象何时发生更改。可以用于乐观并发、更改检测和对资源或资源集的监视操作。客户端必须将这些值视为不透明的,并将未经修改的值传回服务器。它们可能只对特定的资源或资源集有效。 ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` #Generation表示所需状态的特定生成的序列号 Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` #DeletionGracePeriodSeconds在将该对象从系统中删除之前,允许该对象正常终止的秒数。仅当还设置了deletionTimestamp时设置。可能只会缩短。 DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` #Labels可用于组织和分类(范围和选择)对象的字符串键和值的映射。可以匹配复制控制器和服务的选择器。 Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` #注释是一种非结构化的键值映射,存储在资源中,外部工具可以设置该资源来存储和检索任意元数据。它们是不可查询的,在修改对象时应该保留它们。 Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` #由该对象依赖的对象列表,如果列表中的所有对象都有被删除,该对象将被垃圾回收。如果该对象由控制器管理,那么这个列表中的一个条目将指向这个控制器,当控制器字段设置为true时。管理控制器不能多于一个。 OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` #在 k8s 删除对应资源时同时删除关联的外部资源,那么可以通过 Finalizers 来实现。Finalizers 是由字符串组成的列表,当 Finalizers 字段存在时,相关资源不允许被强制删除。存在 Finalizers 字段的的资源对象接收的第一个删除请求设置 metadata.deletionTimestamp 字段的值, 但不删除具体资源,在该字段设置后, finalizer 列表中的对象只能被删除,不能做其他操作。当 metadata.deletionTimestamp 字段非空时,controller watch 对象并执行对应 finalizers 的动作,当所有动作执行完后,需要清空 finalizers ,之后 k8s 会删除真正想要删除的资源。 Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` #ManagedFields包含了唯一的管理器。 管理器由管理实体自身的基本信息组成,比如操作类型、API 版本、以及它管理的字段。机制追踪对象字段的变化。 当一个字段值改变时,其所有权从当前管理器(manager)转移到施加变更的管理器。 ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` }
内容非常常见,当我们执行kubectl descibe pod
或者kubectl get pod ....-oyaml
就能看到这类信息,作为运维k8s人员,大家应该知道他们的含义。
ImageReviewSpec
作为spect
的类型有什么?
如下:
type ImageReviewSpec struct { #Containers是正在创建的Pod的每个容器中的信息子集的列表 Containers []ImageReviewContainerSpec `json:"containers,omitempty" protobuf:"bytes,1,rep,name=containers"` #annotation是从Pod的注释中提取的键值对列表。webhook有时会用到它。 Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,2,rep,name=annotations"` Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` }
ImageReviewStatus
作为Status
的类型包含了什么?
如下:
type ImageReviewStatus struct { #Allowed表示允许运行所有映像 Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` #显示报错信息 Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"` #对象的属性对象将添加AuditAnnotations,允许控制器使用'AddAnnotation',key应该没有前缀(即,允许控制器将添加一个合适的前缀)。 AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,3,rep,name=auditAnnotations"` }
奥,看到这里终于明白了。回到我们的代码中,原来review.Status.Reason是留给我们设置输出报错信息的。
同时呢,我们也根据review.Status.Allowed的源代码库的解释,也明白了它在我们代码中的意义。即是否允许镜像运行的一个判断标志,其实根据代码含义我们也能推测出来。假如allow=true,表达运行镜像运行容器,allow=false,输出错误,并break停止运行程序。
同时,我们也发现了for循环中的imageReview.Spec.Containers
,如果你浏览了代码库,定也明白它的含义,那就是显示正在创建的Pod的每个容器中的信息子集的列表。
我们注意到for循环中有一个container.Image
,它是怎么来的呢?
发现 ImageReviewContainerSpec是有结构体的,里边包含image,同时,ImageReviewContainerSpec作为containers的切片对象,所以我们知道container.Image只是显示pod里的容器镜像,通过apped方法把所有镜像添加images切片中,当然,要提前做好切片初始化images := []string{}。
3. rule目录的not_latest.go
毫无疑问,代码中rule
的方法,我们是引用的不是源代码库,而是本项目中的另一个代码文件的方法,它在rule
目录中。
rules目录下有:
我们找到了,它不叫方法,它叫函数调用。我们看看这个函数里写了什么逻辑。
传入镜像参数,通过调用reference.ParseNormalizedNamed函数返回名字,我们又要看看这个reference.ParseNormalizedNamed到底是什么逻辑,能明白它的准确意思,不能靠猜。所以,我们还有继续寻找代码的源头。
我们找到了这个函数所在的目录,具体是哪个文件呢?
normalize.go
找关键词ParseNormalizedNamed
和normalize.go
有点关联。
果然找到了,注释翻译是:ParseNormalizedNamed
将字符串解析为命名引用,将Docker UI中的熟悉名称转换为完全限定引用。如果值可能是一个标识符,则使用ParseAnyReference
。
没看明白,还是看具体代码逻辑吧
anchoredIdentifierRegexp.MatchString
anchoredIdentifierRegexp.MatchString
又是什么?在这个normalize.go
没有发现,通过关键词Regexp找,应该同目录下的regexp.go
果然又找到了,注释翻译:anchoredIdentifierRegexp用于检查或匹配标识符值,锚定在字符串的开始和结束。问题又来了,anchored(IdentifierRegexp)又是啥,我点慌了,我像个鼹鼠挖地洞。
这个函数翻译描述是通过添加开始和结束分隔符锚定正则表达式。类似传达一个正则表达式的意思。就是分隔符匹配
regexp.Regexp
是包regexp
的结构体,看看是啥,是个空结构体,空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。一是节省资源,二是空结构体本身就具备很强的语义,即这里不需要任何值,仅作为占位符。
奥,看到这里明白一件事,MatchString是Regexp的方法,也就是,Regexp.MatchString是可以调用,而Regexp结构体本身是anchored函数的传参,anchored(ShortIdentifierRegexp)又被定义anchoredShortIdentifierRegexp,所以,根据go语言的传递规则,anchoredShortIdentifierRegexp可以直接调用Regexp的MatchString,即anchoredIdentifierRegexp.MatchString(s),它是什么意思呢?那就 得明白anchored的return的正则匹配规则,负责检查s(字符串)格式是否负责这个规则。也就是说看看这个规则match(^+ expression(res...).String() +$)是什么意思。
代码中
var match = regexp.MustCompile
也就是
regexp.MustCompile(`^` + expression(res...).String() + `$`)
- ^ 代表匹配首
- $ 代表匹配尾
express函数对传递的res的正则符合字符串化,并与字符串s拼接。这里好像是闭包函数的使用。与我们的代码中使用的场景联想一下,正是镜像的格式例如:nginx:latest 、kainlite/kube-image-bouncer:latest,其实express作用的就是镜像中特殊字符为了避免被识别为正则表达式。
回头来看,nchoredIdentifierRegexp.MatchString(s)的作用就是判断s是不是一个镜像。