重新认识Clientset
重新认识Clientset
1、介绍
Clientset 是调用 Kubernetes 资源对象最常用的客户端,可以操作所有的资源对象。
那么在 Clientset 中使如何用这些资源的呢?
因为在 staging/src/k8s.io/api 下面定义了各种类型资源的规范,然后将这些规范注册到了全局的 Scheme 中。这样就可以在Clientset中使用这些资源了。
2、示例
首先我们来看下如何通过 Clientset 来获取资源对象,我们这里来创建一个 Clientset 对象,然后通过该对象来获取默认命名空间之下的 Deployments 列表,代码如下所示:
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
var err error
var config *rest.Config
var kubeconfig *string
if home := 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()
// 使用 ServiceAccount 创建集群配置(InCluster模式)
if config, err = rest.InClusterConfig(); err != nil {
// 使用 KubeConfig 文件创建集群配置
if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {
panic(err.Error())
}
}
// 创建 clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
// 使用 clientsent 获取 Deployments
deployments, err := clientset.AppsV1().Deployments("default").List(metav1.ListOptions{})
if err != nil {
panic(err)
}
for idx, deploy := range deployments.Items {
fmt.Printf("%d -> %s\n", idx+1, deploy.Name)
}
}
func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}
这是一个非常典型的访问 Kubernetes 集群资源的方式,通过 client-go 提供的 Clientset 对象来获取资源数据,主要有以下三个步骤:
- 使用 kubeconfig 文件或者 ServiceAccount(InCluster 模式)来创建访问 Kubernetes API 的 Restful 配置参数,也就是代码中的
rest.Config
对象 - 使用 rest.Config 参数创建 Clientset 对象,这一步非常简单,直接调用
kubernetes.NewForConfig(config)
即可初始化 - 然后是 Clientset 对象的方法去获取各个 Group 下面的对应资源对象进行 CRUD 操作
3、Clientset 对象
上面我们了解了如何使用 Clientset 对象来获取集群资源,接下来我们来分析下 Clientset 对象的实现。
上面我们使用的 Clientset 实际上是对各种资源类型的 Clientset 的一次封装:
// staging/src/k8s.io/client-go/kubernetes/clientset.go
// NewForConfig 通过给定的 config 创建一个新的 Clientset
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.UserAgent == "" {
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
}
// share the transport between all clients
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
if err != nil {
return nil, err
}
return NewForConfigAndClient(&configShallowCopy, httpClient)
}
func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
if configShallowCopy.Burst <= 0 {
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
}
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
}
var cs Clientset
var err error
// 将其他 Group 和版本的资源的 RESTClient 封装到全局的 Clientset 对象中
cs.admissionregistrationV1, err = admissionregistrationv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.appsV1, err = appsv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
......
return &cs, nil
}
上面的 NewForConfig
函数最后调用了NewForConfigAndClient
函数,NewForConfigAndClient
里面就是将其他的各种资源的 RESTClient 封装到了全局的 Clientset 中,这样当我们需要访问某个资源的时候只需要使用 Clientset 里面包装的属性即可,比如 clientset.CoreV1() 就是访问 Core 这个 Group 下面 v1 这个版本的 RESTClient。这些局部的 RESTClient 都定义在 staging/src/k8s.io/client-go/typed///_client.go 文件中,比如 staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go 这个文件中就是定义的 apps 这个 Group 下面的 v1 版本的 RESTClient,这里同样以 Deployment 为例:
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go
func NewForConfig(c *rest.Config) (*AppsV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
httpClient, err := rest.HTTPClientFor(&config)
if err != nil {
return nil, err
}
// 最后还是通过调用 NewForConfigAndClient 函数,返回AppsV1这个资源对象的Clientset
return NewForConfigAndClient(&config, httpClient)
}
func setConfigDefaults(config *rest.Config) error {
// 资源对象的GroupVersion
gv := v1.SchemeGroupVersion
config.GroupVersion = &gv
// 资源对象的根目录
config.APIPath = "/apis"
// 使用注册的资源类型 Scheme 对请求和响应进行编解码,Scheme 就是资源类型的规范
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
return newDeployments(c, namespace)
}
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go
// deployments implements DeploymentInterface
// deployments 实现了 DeploymentInterface 接口
type deployments struct {
client rest.Interface
ns string
}
// newDeployments returns a Deployments
// newDeployments 实例化 deployments 对象
func newDeployments(c *AppsV1Client, namespace string) *deployments {
return &deployments{
client: c.RESTClient(),
ns: namespace,
}
}
通过上面代码我们就可以很清晰的知道可以通过 clientset.AppsV1().Deployments("default")来获取一个 deployments 对象,然后该对象下面定义了 deployments 对象的 CRUD 操作,比如我们调用的 List() 函数:
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go
// List takes label and field selectors, and returns the list of Deployments that match those selectors.
func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
从上面代码可以看出最终是通过 c.client 去发起的请求,也就是局部的 restClient 初始化的函数中通过 rest.RESTClientFor(&config) 创建的对象,也就是将 rest.Config 对象转换成一个 Restful 的 Client 对象用于网络操作:
// staging/src/k8s.io/client-go/rest/config.go
// RESTClientFor返回一个满足客户端Config对象上所要求属性的RESTClient。
// 请注意,在初始化客户端时,一个RESTClient可能需要一些可选的字段。
// 由该方法创建的RESTClient是通用的
// 它期望在遵循Kubernetes惯例的API上操作,但可能不是Kubernetes的API。
// RESTClientFor等同于调用RESTClientForConfigAndClient(config, httpClient)
// 其中httpClient是用HTTPClientFor(config)生成的。
func RESTClientFor(config *Config) (*RESTClient, error) {
if config.GroupVersion == nil {
return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
}
if config.NegotiatedSerializer == nil {
return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
}
// Validate config.Host before constructing the transport/client so we can fail fast.
// ServerURL will be obtained later in RESTClientForConfigAndClient()
_, _, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
httpClient, err := HTTPClientFor(config)
if err != nil {
return nil, err
}
return RESTClientForConfigAndClient(config, httpClient)
}
到这里我们就知道了 Clientset 是基于 RESTClient 的,RESTClient 是底层的用于网络请求的对象,可以直接通过 RESTClient 提供的 RESTful 方法如 Get()、Put()、Post()、Delete() 等和 APIServer 进行交互。
同时支持JSON和protobuf两种序列化方式,支持所有原生资源。
当主食化RESTClient过后,就可以发起网络请求了,比如对于Deployment的List操作:
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go
func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
上面通过调用 RestClient 发起网络请求,真正发起网络请求的代码如下所示:
// staging/src/k8s.io/client-go/rest/request.go
// request connects to the server and invokes the provided function when a server response is
// received. It handles retry behavior and up front validation of requests. It will invoke
// fn at most once. It will return an error if a problem occurred prior to connecting to the
// server - the provided function is responsible for handling server errors.
func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
//Metrics for total request latency
start := time.Now()
defer func() {
metrics.RequestLatency.Observe(ctx, r.verb, *r.URL(), time.Since(start))
}()
if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err)
return r.err
}
if err := r.requestPreflightCheck(); err != nil {
return err
}
// 初始化网络客户端
client := r.c.Client
if client == nil {
client = http.DefaultClient
}
// Throttle the first try before setting up the timeout configured on the
// client. We don't want a throttled client to return timeouts to callers
// before it makes a single request.
if err := r.tryThrottle(ctx); err != nil {
return err
}
// 超时处理
if r.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, r.timeout)
defer cancel()
}
isErrRetryableFunc := func(req *http.Request, err error) bool {
// "Connection reset by peer" or "apiserver is shutting down" are usually a transient errors.
// Thus in case of "GET" operations, we simply retry it.
// We are not automatically retrying "write" operations, as they are not idempotent.
if req.Method != "GET" {
return false
}
// For connection errors and apiserver shutdown errors retry.
if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
return true
}
return false
}
// Right now we make about ten retry attempts if we get a Retry-After response.
// 重试机制
retry := r.retryFn(r.maxRetries)
for {
if err := retry.Before(ctx, r); err != nil {
return retry.WrapPreviousError(err)
}
// 构造请求对象
req, err := r.newHTTPRequest(ctx)
if err != nil {
return err
}
// 发起网络请求
resp, err := client.Do(req)
updateURLMetrics(ctx, r, resp, err)
// The value -1 or a value of 0 with a non-nil Body indicates that the length is unknown.
// https://pkg.go.dev/net/http#Request
if req.ContentLength >= 0 && !(req.Body != nil && req.ContentLength == 0) {
metrics.RequestSize.Observe(ctx, r.verb, r.URL().Host, float64(req.ContentLength))
}
retry.After(ctx, r, resp, err)
done := func() bool {
defer readAndCloseResponseBody(resp)
// if the the server returns an error in err, the response will be nil.
f := func(req *http.Request, resp *http.Response) {
if resp == nil {
return
}
fn(req, resp)
}
if retry.IsNextRetry(ctx, r, req, resp, err, isErrRetryableFunc) {
return false
}
f(req, resp)
return true
}()
if done {
return retry.WrapPreviousError(err)
}
}
}
到这里就完成了一次完整的网络请求。
其实 Clientset 对象也就是将 rest.Config 封装成了一个 http.Client
对象而已,最终还是利用 golang 中的 http 库来执行一个正常的网络请求而已。
重新认识Clientset的更多相关文章
- client-go实战之三:Clientset
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- redis并发问题
redis中的并发问题 使用redis作为缓存已经很久了,redis是以单进程的形式运行的,命令是一个接着一个执行的,一直以为不会存在并发的问题,直到今天看到相关的资料,才恍然大悟~~ 具体问题实例 ...
- redis cluster php 客户端 predis
php有redis的扩展,目前来说,还不支持redis cluster,推荐一下predis,功能比较全,从单个,到主从,到cluster都是支持的.效率怎么样,要靠自己去测试一下. 1,下载pred ...
- [PHP]Yaf + composer 引起大幅性能下降
composer.json 文件可以用命令 composer init 创建,命令是交互式的. 也可以直接编辑一个 json 文件,如下: repositories 中 url 使用中国全量镜像地址. ...
- php修改和增加xml结点属性
<?xml version="1.0" encoding="UTF-8" ?> <clientSet> <server url=& ...
- php 修改、增加xml结点属性的实现代码
php修改xml结点属性,增加xml结点属性的代码,有需要的朋友可以参考下 php 修改 增加xml结点属性的代码,供大家学习参考.php修改xml结点属性,增加xml结点属性的代码,有需要的朋友,参 ...
- inno setup 执行SQL
参考之:1.可将导入数据的功能写入一个小程序,再外部调用(楼上已经说了):2.可用程序代码:[Setup] AppName=科發醫院管理系統 AppVerName=科發醫院管理系統4.0 AppPub ...
- redis 扩展安装使用
Redis的php客户端库非常之多, Redis推荐客户端链接是:http://redis.io/clients 推荐用phpredis,下载地址:https://github.com/nicolas ...
- mfc socket编程
socket编程用法---- 随着计算机网络化的深入,计算机网络编程在程序设计的过程中变得日益重要.由于C++语言对底层操作的优越性,许多文章都曾经介绍过用VC++进行Socket编程的方法.但由于都 ...
- Kubernetes e2e test and test framework
前言 Kubernetes的成功少不了大量工程师的共同参与,而他们之间如何高效的协作,非常值得我们探究.最近研究和使用了他们的e2e测试和框架,还是挺有启发的. 怎样才是好的e2e测试? 不同的人写出 ...
随机推荐
- Laravel11 从0开发 Swoole-Reverb 扩展包(三) - reverb广播驱动使用流程
前情提要 我们第一节的时候,已经大致介绍reverb,他 是 Laravel 应用程序的第一方 WebSocket 服务器,可将客户端和服务器之间的实时通信直接带到您的指尖.开源且只需一个 Artis ...
- python包管理工具pip使用手册
pip是什么? pip 是 Python 标准库管理器,也就是一个工具让你安装不同的类库来使用. 当你要安装某些类库时,都会使用 pip,那 pip 除了安装类库之外,还能做什么呢? 首先,我们进入 ...
- 编写你的第一个 Django 应用程序,第6部分
本教程从教程 5 停止的地方开始.我们已经构建了一个经过测试的网络投票应用程序,现在我们将添加一个样式表和一个图像. 除了服务器生成的 HTML 之外,Web 应用程序通常需要提供呈现完整网页所需的其 ...
- 堆排序(内置模块 heapq )(NB)
博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ import heapq # q->queue 优先队列 import r ...
- DNS+scapy学习
DNS前置知识 大部分介绍转自这篇文章. 官方解释: DNS ( Domain Name System ,域名系统) ,因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联 ...
- bug|初始化项目|sass-loader报错:TypeError: this.getResolve is not a function at Object.loader
Module build failed: TypeError: this.getResolve is not a function at Object.loader的解决 npm uninstall ...
- 当Kafka化身抽水马桶:论组件并发提升与系统可用性的量子纠缠关系
<当Kafka化身抽水马桶:论组件并发提升与系统可用性的量子纠缠关系> 引言:一场OOM引发的血案 某个月黑风高的夜晚,监控系统突然发出刺耳的警报--我们的数据发现流水线集体扑街.事后复盘 ...
- 《机器人SLAM导航核心技术与实战》先导课:课程大纲
<机器人SLAM导航核心技术与实战>先导课:课程大纲 视频讲解 [先导课]1.课程大纲-视频讲解 [先导课]1.1.课程大纲-学习思维导图(上)-视频讲解 [先导课]1.2.课程大纲-学习 ...
- unigui的demo-\Demos\Desktop\DBAppDemo\SimpleDemo.dproj【11】
这个demo很简单. 一个客户表,还有一个票据主从表. 看程序界面: 包括数据提交,彻头彻尾的c/s程序.你完全按照传统的C/S程序模式做开发就可.好处是效率.效率.还是效率! 你还有什么不满意!如果 ...
- ubuntu 22.04安装docker
一.安装 更新软件包索引: sudo apt-get update 允许APT使用HTTPS: sudo apt-get install -y apt-transport-https ca-certi ...