Kubernetes 學(xué)習(xí)筆記---RESTClient 詳解

RESTClient 詳解

RESTClient 是 Kubernetes API 請求的一個(gè)基礎(chǔ)客戶端檩淋,它封裝了最基礎(chǔ)的請求操作懂拾。

接口與結(jié)構(gòu)體

結(jié)構(gòu)體

RESTClient struct 將一些常規(guī)的資源與其進(jìn)行了綁定。

type RESTClient struct {
    //base 提供了一個(gè)最基礎(chǔ)的客戶端的URL夯秃,它通常是從我們傳入的配置文件中獲取,指向配置文件所在的集群。
    base *url.URL
    //versionedAPIPath 指的則是我們的APIPrefix GroupVersion, exp : apis/apps/v1
    versionedAPIPath string
    //content 則是提供了客戶端內(nèi)容的配置课梳,其中包含解碼和編碼器距辆,用于為傳輸?shù)臄?shù)據(jù)進(jìn)行解碼和編碼
    content ClientContentConfig
    //createBackoffMgr 提供了一個(gè)用于創(chuàng)建BackoffManager的方法
    createBackoffMgr func() BackoffManager
    //rateLimiter 則是提供了一個(gè)限速器
    rateLimiter flowcontrol.RateLimiter
    warningHandler WarningHandler
    //http客戶端
    Client *http.Client
}

在ClientContentConfig中提供了解碼和編碼的配置。其結(jié)構(gòu)體如下:

type ClientContentConfig struct {
    //客戶端可接受的內(nèi)容類型
    AcceptContentTypes string
    //ContentType 指定用于與服務(wù)器通信的內(nèi)容格式暮刃。如果沒有設(shè)置AcceptContentTypes挑格,將會使用這個(gè)值作為AcceptContentTypes。
    //如果ContentType沒有設(shè)置沾歪,則默認(rèn)是 "application/json"漂彤。
    ContentType string
    //GroupVersion 提供了資源組和資源版本
    GroupVersion schema.GroupVersion
    //Negotiator 提供了解碼和編碼器
    Negotiator runtime.ClientNegotiator
}

接口

RESTClient Interface 定義了與K8S REST API 交互的一般操作。包常規(guī)的CRUD操作和獲取限速器以及資源版本灾搏。

type Interface interface {
    GetRateLimiter() flowcontrol.RateLimiter
    Verb(verb string) *Request
    Post() *Request
    Put() *Request
    Patch(pt types.PatchType) *Request
    Get() *Request
    Delete() *Request
    APIVersion() schema.GroupVersion
}

接下來我們看看它的接口實(shí)現(xiàn)挫望,我們發(fā)現(xiàn)它的實(shí)現(xiàn)非常簡單,主要是基于 Request 來實(shí)現(xiàn)狂窑。
它們僅僅是去創(chuàng)建一個(gè) Request 然后設(shè)置一下 Verb媳板,所以要想,深入了解泉哈,我們需要去看看 Request 的實(shí)現(xiàn)蛉幸。

func (c *RESTClient) Verb(verb string) *Request {
    return NewRequest(c).Verb(verb)
}

func (c *RESTClient) Post() *Request {
    return c.Verb("POST")
}

func (c *RESTClient) Put() *Request {
    return c.Verb("PUT")
}

func (c *RESTClient) Patch(pt types.PatchType) *Request {
    return c.Verb("PATCH").SetHeader("Content-Type", string(pt))
}

func (c *RESTClient) Get() *Request {...}

func (c *RESTClient) Delete() *Request {...}

Request

結(jié)構(gòu)

通過閱讀Request結(jié)構(gòu)體,我們可以發(fā)現(xiàn)丛晦,在Request中奕纫,它存儲一個(gè)請求的各種信息,包含:

type Request struct {
    //RESTClient rest 客戶端
    c *RESTClient
    warningHandler WarningHandler
    rateLimiter flowcontrol.RateLimiter
    backoff     BackoffManager
    timeout     time.Duration
    //請求動(dòng)作
    verb       string
    //前綴
    pathPrefix string
    //子路徑
    subpath    string
    //參數(shù)
    params     url.Values
    //HTTP頭
    headers    http.Header
    //資源命名空間
    namespace    string
    namespaceSet bool
    //資源類型
    resource     string
    //資源名稱
    resourceName string
    //子資源
    subresource  string
    err   error
    body  io.Reader
    retry WithRetry
}

創(chuàng)建

而在 NewRequest 方法中,我們則可以發(fā)現(xiàn)烫沙,它其實(shí)是將RESTClient中的信息填充到 Request 中匹层。
其他信息則是通過調(diào)用函數(shù)的方法傳遞進(jìn)來。

func NewRequest(c *RESTClient) *Request {
    var backoff BackoffManager
    if c.createBackoffMgr != nil {
        backoff = c.createBackoffMgr()
    }
    if backoff == nil {
        backoff = noBackoff
    }
    
    var pathPrefix string
    if c.base != nil {
        pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath)
    } else {
        pathPrefix = path.Join("/", c.versionedAPIPath)
    }
    
    var timeout time.Duration
    if c.Client != nil {
        timeout = c.Client.Timeout
    }
    
    r := &Request{
        c:              c,
        rateLimiter:    c.rateLimiter,
        backoff:        backoff,
        timeout:        timeout,
        pathPrefix:     pathPrefix,
        retry:          &withRetry{maxRetries: 10},
        warningHandler: c.warningHandler,
    }
    
    switch {
    case len(c.content.AcceptContentTypes) > 0:
        r.SetHeader("Accept", c.content.AcceptContentTypes)
    case len(c.content.ContentType) > 0:
        r.SetHeader("Accept", c.content.ContentType+", */*")
    }
    return r
}

數(shù)據(jù)填充

// SubResource sets a sub-resource path which can be multiple segments after the resource
// name but before the suffix.
func (r *Request) SubResource(subresources ...string) *Request {
    ...
    r.subresource = subresource
    return r
}

// Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Name(resourceName string) *Request {
    ...
    r.resourceName = resourceName
    return r
}

// Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Namespace(namespace string) *Request {
    ...
    r.namespaceSet = true
    r.namespace = namespace
    return r
}

// Param creates a query parameter with the given string value.
func (r *Request) Param(paramName, s string) *Request {
    ...
    return r.setParam(paramName, s)
}

func (r *Request) setParam(paramName, value string) *Request {
    if r.params == nil {
        r.params = make(url.Values)
    }
    r.params[paramName] = append(r.params[paramName], value)
    return r
}

func (r *Request) SetHeader(key string, values ...string) *Request {
    if r.headers == nil {
        r.headers = http.Header{}
    }
    r.headers.Del(key)
    for _, value := range values {
        r.headers.Add(key, value)
    }
    return r
}

func (r *Request) Timeout(d time.Duration) *Request {
    ...
    r.timeout = d
    return r
}
...只展示了部分代碼

URL構(gòu)建

看完了如何為Request設(shè)置值锌蓄,那我們來看看 Request 是如何將這些參數(shù)最終構(gòu)建成用于向K8S服務(wù)請求的URL升筏!
從下面代碼,我們可以清晰的看出瘸爽,Request 構(gòu)建 URL 的方法非常簡單:
先是按照一定的順序您访,將我們開始設(shè)置的namespace,resource,resourceName,subpath 等信息拼接起來得到 URL 的 Path。
然后再將查詢參數(shù)添加進(jìn)去剪决。

// URL returns the current working URL.
func (r *Request) URL() *url.URL {
    //=======拼接 URL.Path========
    p := r.pathPrefix
    if r.namespaceSet && len(r.namespace) > 0 {
        p = path.Join(p, "namespaces", r.namespace)
    }
    if len(r.resource) != 0 {
        p = path.Join(p, strings.ToLower(r.resource))
    }
    // Join trims trailing slashes, so preserve r.pathPrefix's trailing slash for backwards compatibility if nothing was changed
    if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
        p = path.Join(p, r.resourceName, r.subresource, r.subpath)
    }
    
    finalURL := &url.URL{}
    if r.c.base != nil {
        *finalURL = *r.c.base
    }
    finalURL.Path = p
    //=======構(gòu)建 URL.RawQuery========
    query := url.Values{}
    for key, values := range r.params {
        for _, value := range values {
            query.Add(key, value)
        }
    }
    
    // timeout is handled specially here.
    if r.timeout != 0 {
        query.Set("timeout", r.timeout.String())
    }
    finalURL.RawQuery = query.Encode()
    return finalURL
}

另外還有一個(gè) finalURLTemplate 函數(shù)也是用于構(gòu)建 URL 灵汪,它也是基于上述這個(gè)函數(shù)進(jìn)行的構(gòu)建,只是會比上述這個(gè)函數(shù)校驗(yàn)更加嚴(yán)格昼捍,更加符合 K8S 資源 API 路徑規(guī)范识虚。這里就不做詳解了。

發(fā)起請求

構(gòu)建完了URL路徑妒茬,接下我們就來看看如何發(fā)起一個(gè)資源請求担锤。
我們發(fā)起一個(gè)資源請求的方式通常有3種:

  • Do:執(zhí)行某一個(gè)操作。即CRUD乍钻。
  • Watch:監(jiān)聽或觀察一個(gè)數(shù)據(jù)的變動(dòng)肛循。
  • Stream: 獲取數(shù)據(jù)流铭腕。
Do

通過一下代碼,我們可以看出多糠,Do 主要做了兩件事情累舷,一件事情是發(fā)起 request 請求,另一件事情是將響應(yīng)數(shù)據(jù)轉(zhuǎn)換為 Result.

func (r *Request) Do(ctx context.Context) Result {
    var result Result
    // 發(fā)起請求
    err := r.request(ctx, func(req *http.Request, resp *http.Response) {
        // 轉(zhuǎn)換響應(yīng)數(shù)據(jù)
        result = r.transformResponse(resp, req)
    })
    if err != nil {
        return Result{err: err}
    }
    return result
}

發(fā)起請求
先來看看發(fā)起請求時(shí)都做了啥夹孔。它的注釋是這樣說的:

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.

request 將會連接到服務(wù)器被盈,并在接收到服務(wù)器的響應(yīng)時(shí)調(diào)用提供的函數(shù)fn.但至多調(diào)用一次,
他會對請求進(jìn)行預(yù)處理和處理相關(guān)的重試行為.
如果在連接到服務(wù)器前發(fā)生錯(cuò)誤搭伤,將返回錯(cuò)誤只怎。
我們提供的函數(shù)也將負(fù)責(zé)處理服務(wù)器錯(cuò)誤。

func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Response)) error {
    //=====記錄 request 請求的一些 metric 指標(biāo)=====
    start := time.Now()
    defer func() {
        metrics.RequestLatency.Observe(ctx, r.verb, r.finalURLTemplate(), time.Since(start))
    }()
    
    if r.err != nil {
        klog.V(4).Infof("Error in request: %v", r.err)
        return r.err
    }
    /* 請求預(yù)驗(yàn)證怜俐,驗(yàn)證 namespace 為空的情況下身堡,1 。請求不不能為POST拍鲤。
    2 請求為GET贴谎,PUT,DELETE 時(shí)季稳,資源名稱不能為空*/
    if err := r.requestPreflightCheck(); err != nil {
        return err
    }
    
    client := r.c.Client
    if client == nil {
        client = http.DefaultClient
    }
    
    // 為上下文ctx設(shè)置一些超時(shí)的限制
    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()
    }
    
    // Right now we make about ten retry attempts if we get a Retry-After response.
    var retryAfter *RetryAfter
    for {
        //獲取一個(gè) http request 請求擅这。
        req, err := r.newHTTPRequest(ctx)
        if err != nil {
            return err
        }
    
        r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
        if retryAfter != nil {
            // 使用限速器做重試處理
            if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil {
                return err
            }
            retryAfter = nil
        }
        //發(fā)起請求
        resp, err := client.Do(req)
        //更新metric
        updateURLMetrics(ctx, r, resp, err)
        if err != nil {
            r.backoff.UpdateBackoff(r.URL(), err, 0)
        } else {
            r.backoff.UpdateBackoff(r.URL(), err, resp.StatusCode)
        }
    
        done := func() bool {
            //讀取和關(guān)閉響應(yīng)body
            defer readAndCloseResponseBody(resp)
    
            //服務(wù)器響應(yīng)處理,如果響應(yīng)不為空绞幌,將會調(diào)用我們傳遞進(jìn)來的處理方法蕾哟。
            f := func(req *http.Request, resp *http.Response) {
                if resp == nil {
                    return
                }
                fn(req, resp)
            }
    
            //重試處理
            var retry bool
            retryAfter, retry = r.retry.NextRetry(req, resp, err, func(req *http.Request, err error) bool {
               if r.verb != "GET" {
                    return false
                }
                if net.IsConnectionReset(err) || net.IsProbableEOF(err) {
                    return true
                }
                return false
            })
            if retry {
                err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, req.URL.String(), r.body)
                if err == nil {
                    return false
                }
                klog.V(4).Infof("Could not retry request - %v", err)
            }
            // 調(diào)用上面定義好的服務(wù)器響應(yīng)處理
            f(req, resp)
            return true
        }()
        if done {
            return err
        }
    }
}

通過閱讀上面的代碼和注釋,我們可以發(fā)現(xiàn)莲蜘,在發(fā)起 request 請求后,它做了以下動(dòng)作:

  1. request 函數(shù)會先對請求做一些簡單的校驗(yàn)帘营。
  2. 為上下文ctx設(shè)置一些超時(shí)的限制票渠。
  3. 根據(jù)我們的開始構(gòu)建的 Request 結(jié)構(gòu)體數(shù)據(jù), 構(gòu)建一個(gè) http Request 請求芬迄。
  4. 使用限速器做重試處理问顷,處理發(fā)生錯(cuò)誤就退出并返回錯(cuò)誤。
  5. 執(zhí)行請求禀梳。
  6. 重試處理杜窄,
  7. 處理服務(wù)器響應(yīng)數(shù)據(jù),如果服務(wù)器正確響應(yīng)算途,則退出塞耕。響應(yīng)失敗則會回到第3步。

轉(zhuǎn)換響應(yīng)數(shù)據(jù)
轉(zhuǎn)換響應(yīng)數(shù)據(jù)主要是在服務(wù)器成功響應(yīng)時(shí)嘴瓤,使用我們開始提供的解碼器與服務(wù)器響應(yīng)數(shù)據(jù)共同構(gòu)建一個(gè)包含以下信息的結(jié)構(gòu)體:

type Result struct {
    //響應(yīng)內(nèi)容
    body        []byte
    warnings    []net.WarningHeader
    //內(nèi)容類型
    contentType string
    //錯(cuò)誤信息
    err         error
    //http 狀態(tài)碼
    statusCode  int
    //解碼器
    decoder runtime.Decoder
}

同樣的 Result 也為我們提供兩個(gè)方法來獲取數(shù)據(jù),它們都在內(nèi)部調(diào)用解碼器扫外,將數(shù)據(jù)轉(zhuǎn)換為 k8s 的runtime.Object:

func (r Result) Get() (runtime.Object, error) {
    if r.err != nil {
        return nil, r.Error()
    }
    if r.decoder == nil {
        return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType)
    }
    
    // decode, but if the result is Status return that as an error instead.
    out, _, err := r.decoder.Decode(r.body, nil, nil)
    if err != nil {
        return nil, err
    }
    switch t := out.(type) {
    case *metav1.Status:
        if t.Status != metav1.StatusSuccess {
            return nil, errors.FromObject(t)
        }
    }
    return out, nil
}

func (r Result) Into(obj runtime.Object) error {
    ... 和Get相同
    out, _, err := r.decoder.Decode(r.body, nil, obj)
    if err != nil || out == obj {
        return err
    }
    ... 和Get相同
    return nil
}

通過上面的代碼閱讀莉钙,我們知道了 Request 是如何去構(gòu)建并發(fā)送一個(gè)常規(guī)的CURD請求的,并請求完成后如何處理響應(yīng)數(shù)據(jù)的筛谚。

Watch

我們在使用k8s的過程中磁玉,我們往往會使用 Watch 這個(gè)函數(shù)來監(jiān)聽一個(gè)數(shù)據(jù)的變化,以便數(shù)據(jù)發(fā)生修改時(shí)能給及時(shí)感知并同步驾讲。
它是怎么實(shí)現(xiàn)的蚊伞,我們現(xiàn)在就可以來了解以下。

和Do相似吮铭,它會做一些重試設(shè)置厚柳,但它不會對請求進(jìn)行預(yù)校驗(yàn)和使用限速器。

func (r *Request) Watch(ctx context.Context) (watch.Interface, error) {
    // We specifically don't want to rate limit watches, so we
    // don't use r.rateLimiter here.
    if r.err != nil {
        return nil, r.err
    }
    
    client := r.c.Client
    if client == nil {
        client = http.DefaultClient
    }
    
    isErrRetryableFunc := func(request *http.Request, err error) bool {
        if net.IsProbableEOF(err) || net.IsTimeout(err) {
            return true
        }
        return false
    }
    var retryAfter *RetryAfter
    url := r.URL().String()
    for {
        //獲取 HTTP Request
        req, err := r.newHTTPRequest(ctx)
        if err != nil {
            return nil, err
        }
    
        r.backoff.Sleep(r.backoff.CalculateBackoff(r.URL()))
        if retryAfter != nil {
            if err := r.tryThrottleWithInfo(ctx, retryAfter.Reason); err != nil {
                return nil, err
            }
            retryAfter = nil
        }
    
        //執(zhí)行 HTTP Request
        resp, err := client.Do(req)
        updateURLMetrics(ctx, r, resp, err)
        if r.c.base != nil {
            if err != nil {
                r.backoff.UpdateBackoff(r.c.base, err, 0)
            } else {
                r.backoff.UpdateBackoff(r.c.base, err, resp.StatusCode)
            }
        }
        // 如果請求成功 創(chuàng)建并返回一個(gè)數(shù)據(jù)流觀察者---StreamWatcher
        if err == nil && resp.StatusCode == http.StatusOK {
            return r.newStreamWatcher(resp)
        }
    
        // 請求失敗沐兵,重試處理
        // 可以重試就繼續(xù)重試别垮,不可以重試就處理響應(yīng)數(shù)據(jù)并返回錯(cuò)誤。
        done, transformErr := func() (bool, error) {
            defer readAndCloseResponseBody(resp)
    
            var retry bool
            retryAfter, retry = r.retry.NextRetry(req, resp, err, isErrRetryableFunc)
            if retry {
                err := r.retry.BeforeNextRetry(ctx, r.backoff, retryAfter, url, r.body)
                if err == nil {
                    return false, nil
                }
                klog.V(4).Infof("Could not retry request - %v", err)
            }
    
            if resp == nil {
                // the server must have sent us an error in 'err'
                return true, nil
            }
            if result := r.transformResponse(resp, req); result.err != nil {
                return true, result.err
            }
            return true, fmt.Errorf("for request %s, got status: %v", url, resp.StatusCode)
        }()
        if done {
            // 對于一些常見的網(wǎng)絡(luò)錯(cuò)誤扎谎,直接返回一個(gè)空的觀察者
            if isErrRetryableFunc(req, err) {
                return watch.NewEmptyWatch(), nil
            }
            if err == nil {
                err = transformErr
            }
            return nil, err
        }
    }
}

接下來我們看看他是怎么創(chuàng)建數(shù)據(jù)流觀察者(StreamWatcher)的碳想。

  1. 獲取 contentType。
  2. 將 contentTyp 解析為 mediaType 和 params毁靶。
  3. 根據(jù) mediaType 和 params 從 Request 的流式 StreamDecoder 中獲取 objectDecoder胧奔,streamingSerializer,framer预吆。
  4. 獲取 frameReader龙填,watchEventDecoder。
  5. 調(diào)用 watch.NewStreamWatcher 創(chuàng)建 StreamWatcher拐叉。
func (r *Request) newStreamWatcher(resp *http.Response) (watch.Interface, error) {
    contentType := resp.Header.Get("Content-Type")
    mediaType, params, err := mime.ParseMediaType(contentType)
    if err != nil {
        klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err)
    }
    objectDecoder, streamingSerializer, framer, err := r.c.content.Negotiator.StreamDecoder(mediaType, params)
    if err != nil {
        return nil, err
    }
    
    handleWarnings(resp.Header, r.warningHandler)
    
    frameReader := framer.NewFrameReader(resp.Body)
    watchEventDecoder := streaming.NewDecoder(frameReader, streamingSerializer)
    
    return watch.NewStreamWatcher(
        restclientwatch.NewDecoder(watchEventDecoder, objectDecoder),
        errors.NewClientErrorReporter(http.StatusInternalServerError, r.verb, "ClientWatchDecoding"),
    ), nil
}

然后我們通過查看NewStreamWatcher 會發(fā)現(xiàn)岩遗,這個(gè)在函數(shù)返回了一個(gè)StreamWatcher的同時(shí),開啟了一個(gè)協(xié)程進(jìn)行服務(wù)凤瘦。
在協(xié)程中不斷的通過資源解碼器獲取資源和事件類型宿礁,然后通過通道將數(shù)據(jù)傳輸出去。

func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {
    sw := &StreamWatcher{
        source:   d,
        reporter: r,
        result: make(chan Event),
        done: make(chan struct{}),
    }
    go sw.receive()
    return sw
}
// receive reads result from the decoder in a loop and sends down the result channel.
func (sw *StreamWatcher) receive() {
    defer utilruntime.HandleCrash()
    defer close(sw.result)
    defer sw.Stop()
    for {
        action, obj, err := sw.source.Decode()
        if err != nil {
            switch err {
            case io.EOF:
            case io.ErrUnexpectedEOF:
                klog.V(1).Infof("Unexpected EOF during watch stream event decoding: %v", err)
            default:
                if net.IsProbableEOF(err) || net.IsTimeout(err) {
                    klog.V(5).Infof("Unable to decode an event from the watch stream: %v", err)
                } else {
                    select {
                    case <-sw.done:
                    case sw.result <- Event{
                        Type:   Error,
                        Object: sw.reporter.AsObject(fmt.Errorf("unable to decode an event from the watch stream: %v", err)),
                    }:
                    }
                }
            }
            return
        }
        select {
        case <-sw.done:
            return
        case sw.result <- Event{
            Type:   action,
            Object: obj,
        }:
        }
    }
}
Stream

Stream 是用來獲取一個(gè)數(shù)據(jù)流蔬芥,它的做法相較于watch而言梆靖,則是比較簡單的,
僅僅只是在請求成功時(shí)返回 IO Reader笔诵。

func (r *Request) Stream(ctx context.Context) (io.ReadCloser, error) {
    ...
    url := r.URL().String()
    for {
        req, err := r.newHTTPRequest(ctx)
        ...

        resp, err := client.Do(req)
        ...

        switch {
        case (resp.StatusCode >= 200) && (resp.StatusCode < 300):
            handleWarnings(resp.Header, r.warningHandler)
            return resp.Body, nil

        default:
            ...
        }
    }
}

經(jīng)過上面的介紹返吻,我們應(yīng)該大概解了RESTClient是怎么樣工作的了,但是為什么是k8s其他客戶端的基礎(chǔ)呢乎婿?我們可以通過一個(gè)例子看出來:
我們對比一下使用 RESTClient , ClientSet 以及 DynamicClient 來獲取pod测僵,大家應(yīng)該就理解了。
我們可以看到次酌,ClientSet恨课,DynamicClient 最終都是調(diào)用 RESTClient 里面的 方法來進(jìn)行請求舆乔。

//獲取配置
func getConfig() *rest.Config {
    config, err := clientcmd.BuildConfigFromFlags("", "xxx")
    if err != nil {
        panic(err)
    }
    return config
}

RESTClient

func restClient() {
    //config
    config := getConfig()
    config.GroupVersion = &v1.SchemeGroupVersion
    config.NegotiatedSerializer = scheme.Codecs
    config.APIPath = "/api"
    //client
    client, err := rest.RESTClientFor(config)
    if err != nil {
        panic(err)
    }
    // get data
    var pod v1.Pod
    req := client.Get().
        Namespace("default").
        Resource("pods").
        Name("xxx").
        Do(context.TODO()).
        Into(&pod)
    if err != nil {
        fmt.Println(err)
    } else {    
        fmt.Println(pod.Name)
    }
}

ClientSet

func clientCmd() {
    config := getConfig()
    clientSet, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err)
    }
    pod, err := clientSet.CoreV1().Pods("default").Get(context.TODO(), "xxx", v12.GetOptions{})
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(pod.Name)
    }
}

func (c *pods) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Pod, err error) {
    result = &v1.Pod{}
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        Name(name).
        VersionedParams(&options, scheme.ParameterCodec).
        Do(ctx).
        Into(result)
    return
}

type pods struct {
    client rest.Interface
    ns     string
}

// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
    return &pods{
        client: c.RESTClient(),
        ns:     namespace,
    }
}

DynamicClient

func dynamicCli() {
    resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
    config := getConfig()
    cli, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err)
    }
    pod, err := cli.Resource(resource).Namespace("default").Get(context.TODO(), "xxx",v12.GetOptions{})
    if err != nil {
        fmt.Println(err)
    }  else {
        fmt.Println(pod.GetName())
    }
}

func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
    if len(name) == 0 {
        return nil, fmt.Errorf("name is required")
    }
    result := c.client.client.Get().
        AbsPath(append(c.makeURLSegments(name), subresources...)...).
        SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
        Do(ctx)
    if err := result.Error(); err != nil {
        return nil, err
    }
    retBytes, err := result.Raw()
    if err != nil {
        return nil, err
    }
    uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes)
    if err != nil {
        return nil, err
    }
    return uncastObj.(*unstructured.Unstructured), nil
}

type dynamicClient struct {
    client *rest.RESTClient
}

type dynamicResourceClient struct {
    client    *dynamicClient
    namespace string
    resource  schema.GroupVersionResource
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剂公,隨后出現(xiàn)的幾起案子希俩,更是在濱河造成了極大的恐慌,老刑警劉巖纲辽,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颜武,死亡現(xiàn)場離奇詭異,居然都是意外死亡拖吼,警方通過查閱死者的電腦和手機(jī)鳞上,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吊档,“玉大人篙议,你說我怎么就攤上這事〉∨穑” “怎么了鬼贱?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長香璃。 經(jīng)常有香客問我这难,道長,這世上最難降的妖魔是什么葡秒? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任姻乓,我火速辦了婚禮,結(jié)果婚禮上眯牧,老公的妹妹穿的比我還像新娘蹋岩。我一直安慰自己,他們只是感情好炸站,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布星澳。 她就那樣靜靜地躺著,像睡著了一般旱易。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腿堤,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天阀坏,我揣著相機(jī)與錄音,去河邊找鬼笆檀。 笑死忌堂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酗洒。 我是一名探鬼主播士修,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼枷遂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棋嘲?” 一聲冷哼從身側(cè)響起酒唉,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沸移,沒想到半個(gè)月后痪伦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雹锣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年网沾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕊爵。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辉哥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攒射,到底是詐尸還是另有隱情醋旦,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布匆篓,位于F島的核電站浑度,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸦概。R本人自食惡果不足惜箩张,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窗市。 院中可真熱鬧先慷,春花似錦、人聲如沸咨察。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摄狱。三九已至脓诡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媒役,已是汗流浹背祝谚。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酣衷,地道東北人交惯。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親席爽。 傳聞我的和親對象是個(gè)殘疾皇子意荤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容