惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
博客园 - 聂微东
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog RSS Feed
小众软件
小众软件
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
V
V2EX
B
Blog
H
Help Net Security
D
Docker
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
月光博客
月光博客
博客园 - 司徒正美

又耳笔记

用AI挣钱之AI建站:二维码生成器工具 用AI挣钱之AI建站:https://markdowntopdf.top Cloudflare Workers实战番外一:用Static Assets托管网站,告别传统方案 Cloudflare Workers实战(五):不止JavaScript,拥抱Python与Rust Cloudflare Workers实战(四):托管和分发静态文件 Cloudflare Workers实战(三):实现认证、重定向与缓存 Cloudflare Workers实战(二):动态修改后端响应 Cloudflare Workers实战(一):随心所欲操作客户端请求 Cloudflare workers不完全指南 使用n8n创作短篇小说 水果风波:信任的代价 用Python将PDF文件转换成图片 rust网络框架Pingora源码阅读3 rust网络框架Pingora源码阅读2 rust网络框架Pingora源码阅读1 Pingora快速入门教程1之总览 Rust模板引擎askama快速入门引擎 使用Loco快速搭建自己的后台系统 用Rust发一封图文并茂的邮件 Rust小项目: 写一个简单的恶意流量阻断器 Rust小项目: 写一个简单的网页爬虫 Rust命令行库Clap快速入门教程 Rust小项目:用Rust写一个端口扫描器 Rust真全栈开发快速入门 rust声明宏快速入门教程 Rust文本处理快速入门教程 RUST web框架axum快速入门教程6之测试 白嫖免费的Rust在线运行时shuttle RUST web框架axum快速入门教程5之中间件 用Rust来做以太坊开发5之事件日志及签名
k8s client-go快速入门教程及源代码阅读之发现客户端
About 又耳宁 关于技术与股票的一些零碎想法 · 2023-07-15 · via 又耳笔记

之前介绍了client-go的静态客户端,动态客户端,本文主要讲解一下发现客户端的源代码,发现客户端用于发现k8s集群现有的资源,包括但不限于内置资源,可以获取当时k8s集群中的所有资源,通过发现客户端我们可以确定当前器群存在哪些资源及其对应的版本。

快速入门

不知道大家有没有一个疑惑,在使用kubectl的时候我们不会指定资源的版本,但是kubectl怎么通过GVR/GVK定位资源呢? 其实这个问题可以由发现客户端回答,它的接口返回数据中有ServerPreferredResources, 即服务端首选资源。

kubectl会在发现客户端之上在封装一层缓存,因为每次直接从服务器获取比较消耗服务端资源。

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"path/filepath"

	"k8s.io/client-go/discovery"

	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

func printJson(obj interface{}) {
	data, err := json.MarshalIndent(obj, "", "  ")
	if err != nil {
		log.Fatal("序列化失败!", err)
	}
	fmt.Println(string(data))
}

func main() {
	var kubeconfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		log.Fatal(err)
	}

	discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(config)
	serverGroups, err := discoveryClient.ServerGroups()
	if err != nil {
		log.Fatal(err)
	}
	printJson(serverGroups)
	serverPreferredResources, err := discoveryClient.ServerPreferredResources()
	if err != nil {
		log.Fatal(err)
	}
	printJson(serverPreferredResources)
	serverPreferredNamespacedResources, err := discoveryClient.ServerPreferredNamespacedResources()
	if err != nil {
		log.Fatal(err)
	}
	printJson(serverPreferredNamespacedResources)
}

输出如下:

服务端首选资源组:{
  "kind": "APIGroupList",
  "apiVersion": "v1",
  "groups": [
    {
      "name": "apps",
      "versions": [
        {
          "groupVersion": "apps/v1",
          "version": "v1"
        }
      ],
      "preferredVersion": {
        "groupVersion": "apps/v1",
        "version": "v1"
      }
    },
    // 省略...
}
服务端首选资源:[
  {
    "groupVersion": "v1",
    "resources": [
      {
        "name": "componentstatuses",
        "singularName": "",
        "namespaced": false,
        "kind": "ComponentStatus",
        "verbs": [
          "get",
          "list"
        ],
        "shortNames": [
          "cs"
        ]
      },
      // 省略...
}
服务端首选命名空间级别资源:[
  {
    "groupVersion": "v1",
    "resources": [
      {
        "name": "persistentvolumeclaims",
        "singularName": "",
        "namespaced": true,
        "kind": "PersistentVolumeClaim",
        "verbs": [
          "create",
          "delete",
          "deletecollection",
          "get",
          "list",
          "patch",
          "update",
          "watch"
        ],
        "shortNames": [
          "pvc"
        ],
        "storageVersionHash": "QWTyNDq0dC4="
      },
      // 省略
}

发现客户端的使用是比较简单的,所以这段代码就不需要更多的解释了。

手动请求

其实也可以手动获取k8s集群内的所有资源版本信息

首先使用下面命令,代理k8s请求,这样就不需要认证了。

kubectl proxy
# 输出如下
Starting to serve on 127.0.0.1:8001

然后访问/api/v1路径, 即列出core组的v1版本所有资源

输出和上面是差不多的,所以这里就不赘述了。

客户端构造

代码如下:

discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(config)

func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) {
    // 浅拷贝
	config := *c
    // UnversionedRESTClientFor  即不指定版本
	client, err := restclient.UnversionedRESTClientFor(&config)
	return &DiscoveryClient{restClient: client, LegacyPrefix: "/api"}, err
}

func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
	// 前面和RESTClient几乎一致
    // 这里的gv是meta.k8s.io
	gv := metav1.SchemeGroupVersion
	if config.GroupVersion != nil {
		gv = *config.GroupVersion
	}
	clientContent := ClientContentConfig{
		AcceptContentTypes: config.AcceptContentTypes,
		ContentType:        config.ContentType,
		GroupVersion:       gv,
		Negotiator:         runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
	}
	restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
	return restClient, err
}

构造的过程和RESTClient几乎一致,不同点主要是gv和Negotiator。

资源请求

这里以ServerGroups的资源请求为例,其他两个大致是差不多的。

代码如下:

func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
	// 1. 
	v := &metav1.APIVersions{}
	err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do(context.TODO()).Into(v)
	apiGroup := metav1.APIGroup{}
	if err == nil && len(v.Versions) != 0 {
		apiGroup = apiVersionsToAPIGroup(v)
	}

	// 2.
	apiGroupList = &metav1.APIGroupList{}
	err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList)
	if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
		return nil, err
	}

	// 3.
	if len(v.Versions) != 0 {
		apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...)
	}
	return apiGroupList, nil
}

代码解析如下:

  1. 请求/api路径的资源,即列出core组下的所有资源
  2. 请求/apis路径下的资源,即列出其他组的所有资源
  3. 将两者组合到一起

总结

由于k8s是一个可扩展的平台,所以不能够硬编码资源版本信息,因此k8s提供了发现机制用于客户端查询当前集群中的所有资源,值得注意的是,使用kubectl如果查询不到自己添加的CRD, 可能是缓存的原因。

参考链接