Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【4】main.go源码解析(1)

简介: Kubernetes ImagePolicyWebhook与ValidatingAdmissionWebhook【4】main.go源码解析(1)

文章目录

1. 包的依赖

2. main.go

2.1 先分析cli.v1的应用

2.2 LoggerWithConfig是这么含义?

2.2.1 `fasttemplate.New(config.Format, "${", "}")`是什么?

2.2.2 `config.colorer = color.New()`代码中`new()`又如何理解呢?

2.2.3 sync.Pool又是什么?

2.3 白名单、证书、密钥的判断

3. 总结相关阅读:


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. 包的依赖

go fmt包详解

go os包文件操作详解

go strings包详解

echo包详解

echo源码

https://pkg.go.dev/io

https://pkg.go.dev/os

项目源码:https://github.com/kainlite/kube-image-bouncer

2. main.go

首先我们了解一下依赖包的各个用途。

import (
  "fmt"  //打印输出
  "os"  //系统管理
  "strings"  //字符串管理
  "github.com/labstack/echo"  //可扩展,轻量级的web框架
  "github.com/labstack/echo/middleware"  //echo web框架中间件
  "github.com/labstack/gommon/log"  //自定义的日志输出
  "gopkg.in/urfave/cli.v1"   //用于在 Go 中构建命令行应用程序
  "github.com/kainlite/kube-image-bouncer/handlers"
)

下面的代码其实是以cli包与echo包为主的运行,如果具体学习cli的用法可以参考https://pkg.go.dev/gopkg.in/urfave/cli.v1

func main() {
//定义参数
  var cert, key, whitelist string  //证书,密钥,白名单
  var port int  //webhook的端口
  var debug bool //debug模式
//定义命令对象
  app := cli.NewApp()
  app.Name = "kube-image-bouncer"
  app.Usage = "webhook endpoint for kube image policy admission controller"
//定义命令参数
  app.Flags = []cli.Flag{
    cli.StringFlag{
      Name:        "cert, c",
      Usage:       "Path to the certificate to use",
      EnvVar:      "BOUNCER_CERTIFICATE",
      Destination: &cert,
    },
    cli.StringFlag{
      Name:        "key, k",
      Usage:       "Path to the key to use",
      EnvVar:      "BOUNCER_KEY",
      Destination: &key,
    },
    cli.StringFlag{
      Name:        "registry-whitelist",
      Usage:       "Comma separated list of accepted registries",
      EnvVar:      "BOUNCER_REGISTRY_WHITELIST",
      Destination: &whitelist,
    },
    cli.IntFlag{
      Name:        "port, p",
      Value:       1323,
      Usage:       "Port to listen to",
      EnvVar:      "BOUNCER_PORT",
      Destination: &port,
    },
    cli.BoolFlag{
      Name:        "debug",
      Usage:       "Enable extra debugging",
      EnvVar:      "BOUNCER_DEBUG",
      Destination: &debug,
    },
  }
//定义命令的调用方式
  app.Action = func(c *cli.Context) error {
    e := echo.New()
    e.POST("/image_policy", handlers.PostImagePolicy())
    e.POST("/", handlers.PostValidatingAdmission())
    e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
      Format: "method=${method}, uri=${uri}, status=${status}\n",
    }))
    if debug {
      e.Logger.SetLevel(log.DEBUG)
    }
    if whitelist != "" {
      handlers.RegistryWhitelist = strings.Split(whitelist, ",")
      fmt.Printf(
        "Accepting only images from these registries: %+v\n",
        handlers.RegistryWhitelist)
      fmt.Println("WARN: this feature is implemented only by the ValidatingAdmissionWebhook code")
    } else {
      fmt.Println("WARN: accepting images from ALL registries")
    }
    var err error
    if cert != "" && key != "" {
      err = e.StartTLS(fmt.Sprintf(":%d", port), cert, key)
    } else {
      err = e.Start(fmt.Sprintf(":%d", port))
    }
    if err != nil {
      return cli.NewExitError(err, 1)
    }
    return nil
  }
//运行app对象
  app.Run(os.Args)
}

main.go主要设计两个依赖包的用法:

  • cli.v1
  • echo

2.1 先分析cli.v1的应用

创建一个newappgo build 生成二进制命令则是app.name的值。

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

这里是除了配置命令的名字,用法说明,还有配置命令行参数,这里有包含四个,分别是证书,密钥、白名单、端口。,白名单是我们需要的仓库,即来自白名单的仓库镜像才是我们可以用的,端口是我们程序占用的端口。那证书与密钥又是用来开启https

image.png

每个参数进行设置详细的类型说明。

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

e := echo.New()创建一个名叫e的实例。

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

e.use是使用将中间件添加到在路由器之后运行的链中,也就是说这里是自定义log输出。

具体解释参考https://studygolang.com/articles/11740

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

2.2 LoggerWithConfig是这么含义?

它来自github.com/labstack/echo/middleware,通过查找,在https://github.com/labstack/echo/blob/master/middleware/logger.go文件中。描述功能为一个中间件配置的日志。如下:

type (
  // LoggerConfig defines the config for Logger middleware.
  LoggerConfig struct {
    // Skipper defines a function to skip middleware.
    Skipper Skipper
    Format string `yaml:"format"`
    // Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
    CustomTimeFormat string `yaml:"custom_time_format"`
    // Output is a writer where logs in JSON format are written.
    // Optional. Default value os.Stdout.
    Output io.Writer
    template *fasttemplate.Template
    colorer  *color.Color
    pool     *sync.Pool
  }
)
var (
  // DefaultLoggerConfig is the default Logger middleware config.
  DefaultLoggerConfig = LoggerConfig{
    Skipper: DefaultSkipper,
    Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
      `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
      `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
      `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
    CustomTimeFormat: "2006-01-02 15:04:05.00000",
    colorer:          color.New(),
  }
)
// LoggerWithConfig returns a Logger middleware with config.
// See: `Logger()`.
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
  // Defaults
  if config.Skipper == nil {
    config.Skipper = DefaultLoggerConfig.Skipper
  }
  if config.Format == "" {
    config.Format = DefaultLoggerConfig.Format
  }
  if config.Output == nil {
    config.Output = DefaultLoggerConfig.Output
  }
  config.template = fasttemplate.New(config.Format, "${", "}")
  config.colorer = color.New()
  config.colorer.SetOutput(config.Output)
  config.pool = &sync.Pool{
    New: func() interface{} {
      return bytes.NewBuffer(make([]byte, 256))
    },
  }

middleware.LoggerConfig{Format: "method=${method}, uri=${uri}, status=${status}\n",}等于config LoggerConfig


config.Skipper并没有明确的定义,但这里是给予了默认值DefaultLoggerConfig.Skipper即defaultSkipper,暂认为是一种配置标识。

config.Format给出了输出选择属性,即method=${method}, uri=${uri}, status=${status}\n

config.Output默认输出一种json格式。


当我们推理到这里的时候,就会发现我们又要开启对各个莫名其妙的调用包的理解之旅了,因为它是我们的成长之路。


首先我们先明确一下LoggerWithConfig函数传入了什么?返回了什么?以什么格式返回输出?


传入了什么?config LoggerConfig代码已写出。

返回了什么?bytes.NewBuffer(make([]byte, 256))

1035234-20181020215539574-213176954.png

  • 以什么格式返回输出? echo.MiddlewareFunc

1035234-20181020215539574-213176954.png

type Context interface {
  Request() *http.Request
  SetRequest(r *http.Request)
  Response() *Response
  IsTLS() bool
  IsWebSocket() bool
  Scheme() string
  RealIP() string
  Path() string
  SetPath(p string)
  Param(name string) string
  ParamNames() []string
  SetParamNames(names ...string)
  ParamValues() []string
  SetParamValues(values ...string)
  QueryParam(name string) string
  QueryParams() url.Values
  QueryString() string
  FormValue(name string) string
  FormParams() (url.Values, error)
  FormFile(name string) (*multipart.FileHeader, error)
  MultipartForm() (*multipart.Form, error)
  Cookie(name string) (*http.Cookie, error)
  SetCookie(cookie *http.Cookie)
  Cookies() []*http.Cookie
  Get(key string) interface{}
  Set(key string, val interface{})
  Bind(i interface{}) error
  Validate(i interface{}) error
  Render(code int, name string, data interface{}) error
  HTML(code int, html string) error
  HTMLBlob(code int, b []byte) error
  String(code int, s string) error
  JSON(code int, i interface{}) error
  JSONPretty(code int, i interface{}, indent string) error
  JSONBlob(code int, b []byte) error
  JSONP(code int, callback string, i interface{}) error
  JSONPBlob(code int, callback string, b []byte) error
  XML(code int, i interface{}) error
  XMLPretty(code int, i interface{}, indent string) error
  XMLBlob(code int, b []byte) error
  Blob(code int, contentType string, b []byte) error
  Stream(code int, contentType string, r io.Reader) error
  File(file string) error
  Attachment(file string, name string) error
  Inline(file string, name string) error
  NoContent(code int) error
  Redirect(code int, url string) error
  Error(err error)
  Handler() HandlerFunc
  SetHandler(h HandlerFunc)
  Logger() Logger
  Echo() *Echo
  Reset(r *http.Request, w http.ResponseWriter)
}

NewBuffer使用buf作为它的初始内容来创建和初始化一个新的Buffer。新的Buffer获得了buf的所有权,调用者在调用之后不应该使用buf。NewBuffer用于准备一个Buffer来读取现有数据。它还可以用于设置用于写入的内部缓冲区的初始大小。要做到这一点,buf应该具有所需的容量,但长度为零。


再次,我们分析LoggerWithConfig的过程

2.2.1 fasttemplate.New(config.Format, "${", "}")是什么?

来自https://github.com/valyala/fasttemplate/blob/master/template.go

New使用给定的startTag和endTag作为标签开始和结束来解析给定的模板。返回的模板可以通过使用Execute*方法并发运行goroutines来执行。如果给定的模板无法解析,则会出现新的恐慌。如果模板可能包含错误,请使用NewTemplate。


config.Format即是template

"${", "}"即是开始结束标识符

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

1035234-20181020215539574-213176954.png

reset方法中重定义t结构体的变量名后,利用 unsafeString2Bytes()对各个变量进行了处理,那么它的逻辑什么?

fasttemplate包中的unsafe.go代码我们找到了。

1035234-20181020215539574-213176954.png

这里用到了reflect包和unsafe包,他们的作用分别是:


reflect:反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。

unsafe: 指向不同类型数据的指针,是无法直接相互转换的,必须借助unsafe.Pointer

1035234-20181020215539574-213176954.png

StringHeader是字符串的运行时表示。它不能安全或便携地使用,它的表示形式可能会在以后的版本中改变。而且,Data字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保持一个独立的、正确键入的指向底层数据的指针.

1035234-20181020215539574-213176954.png

Uintptr是一个整数类型,它大到足以容纳任何指针的位模式

len当然是整数类型

1035234-20181020215539574-213176954.png

SliceHeader是片的运行时表示。它不能安全或便携地使用,它的表示形式可能会在以后的版本中改变。而且,Data字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保持一个独立的、正确键入的指向底层数据的指针。


unsafeString2Bytes其实就是将字符串的格式转换为切片类型,而go格式转换需要unsafe.Point方法。


分析到这里,我们要倒推一下s是什么?

unsafeString2Bytes(s string)---->unsafeString2Bytes(template)----template是Template的结构体一部分----->New(template, startTag, endTag string)------------>fasttemplate.New(config.Format, "${", "}")---->LoggerWithConfig(config LoggerConfig) ------>iddleware.LoggerWithConfig(middleware.LoggerConfig{

Format: “method=m e t h o d , u r i = {method}, uri=method,uri={uri}, status=${status}\n”,

})


template即是Format: "method=${method}, uri=${uri}, status=${status}\n"


回到fasttemplate代码中的reset函数中,继续往下分析。


当s、a、b都转换为切片格式之后,利用bytes.count()方法统计s中a的数量。

随后,对t.texts与t.tags初始化切片分配tagcount内存容量。

判断s中是否有a起始标志并返回它的位置,如果为空不存在,即返回-1,当<0时,直接将s合并给t.texts格式并退出。

如果n>0,把s中第一个a之前的切片输出给t.texts

s = s[n+len(a):]则是重定义s中出现a之后的切片重新赋值给s

然后判断s中是否有b结尾标志的数量,假如没有,则报错。因为有起始必有结尾才行。

append(t.tags, unsafeBytes2String(s[:n]))是把起始到结尾之间的切片赋值给t.tags,

最后s再次重新定义为s中b之后的切片内容。

回到包含reset函数的NewTemplate函数中看,NewTemplate其实是通过"${"与"}"对template格式进行一次检验,因为reset没有确切的返回值,只是返回是否报错而已,但是reset设定了t结构体的属性值内容,虽然没有在NewTemplate返回输出,但包含NewTemplate函数的New函数输出了。t结构体包含的template、startTag、endTag、texts、tags 都有了具体内容。


new函数返回输出在echo.middleware代码中LoggerWithConfig函数有体现,给了config.template,即属于*fasttemplate.Template类型,是原来t的同一个结构体而已。


我们继续分析LoggerWithConfig。


相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
749 90
|
存储 自然语言处理 算法
【LeetCode 热题100】208:实现 Trie (前缀树)(详细解析)(Go语言版)
本文详细解析了力扣热题 208——实现 Trie(前缀树)。Trie 是一种高效的树形数据结构,用于存储和检索字符串集合。文章通过插入、查找和前缀匹配三个核心操作,结合 Go 语言实现代码,清晰展示了 Trie 的工作原理。时间复杂度为 O(m),空间复杂度也为 O(m),其中 m 为字符串长度。此外,还探讨了 Trie 的变种及应用场景,如自动补全和词典查找等。适合初学者深入了解 Trie 结构及其实际用途。
476 14
|
SQL 运维 监控
高效定位 Go 应用问题:Go 可观测性功能深度解析
为进一步赋能用户在复杂场景下快速定位与解决问题,我们结合近期发布的一系列全新功能,精心梳理了一套从接入到问题发现、再到问题排查与精准定位的最佳实践指南。
|
存储 机器学习/深度学习 缓存
🚀 力扣热题 394:字符串解码(详细解析)(Go语言版)
文章提供了两种解法:栈结构和递归解法。栈解法通过维护数字栈与字符串栈,依次处理 `[` 和 `]`,构造解码结果;递归解法则利用函数调用逐层解析嵌套结构。两者时间复杂度均为 $O(n)$,空间复杂度也为 $O(n)$。栈解法直观易懂,适合初学者;递归解法优雅简洁,适合处理深度嵌套规则。掌握这两种方法,可灵活应对类似问题,提升解题能力。
452 11
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
528 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
511 2
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1288 29
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。

推荐镜像

更多
  • DNS