Client-goソースコード分析(1):client-goクライアントオブジェクト
クライアントゴーの重要性
Client-goはK8sクラスターのセカンダリ開発ツールであるため、client-goはk8s開発者に必要なツールの1つです。
この記事の目的
この記事の目的は、k8sクラスタークライアントの種類を説明することです。以前にclient-goを使用してk8sのリソースオブジェクトを操作したことがあるかもしれませんが、原則については少し理解しているかもしれません。client-gogithubは詳細な説明のないいくつかの簡単な例。この講義では、最初に紹介します。client-goには、クラスターと対話するためのいくつかの種類のクライアントオブジェクト、これらのクライアントオブジェクトの用途、およびそれらの対話の原則は何ですか。
クライアントオブジェクト
異なる機能を持つ4つのclient-goクライアントオブジェクトがあります。
- RESTClient:HTTPリクエストをカプセル化し、RESTfulスタイルのAPIを実装します。他のクライアントは、RESTClientに基づいて実装されます。k8sの組み込みリソースとCRDリソースで使用できます
- ClientSet:k8s組み込みリソースオブジェクトのクライアントのコレクションです。デフォルトでは、CRDリソースは操作できませんが、client-genコードによって生成された場合はCRDリソースも操作できます。
- DynamicClient:K8Sの組み込みリソースを処理できるだけでなく、CRDリソースも処理できます。これは、クライアント生成コードを生成せずに実現できます。
- DiscoveryClient:kube-apiserverでサポートされているリソースグループ、リソースバージョン、およびリソース情報(つまり、グループ、バージョン、リソース)を検出するために使用されます。
このセクションの概要:RESTCLient、ClientSet、DynamicClientはすべて、K8S組み込みリソースとCRDリソースで動作できます。clientSetのみが、CRDリソースを操作するためのコードを生成する必要があります。
RESTClient
RESTClientはHTTPリクエストをカプセル化し、RESTful APIカプセル化を実装するため、ユーザーはK8Sリソースを取得するときに元のAPIメソッドを使用する必要がありません(たとえば、リクエストhttps://1.142.113.45:6443/api/v1/ポッドなどの元のフォーム) 。ClientSetのクライアントは、コードをより読みやすく、使いやすくします。次に例を示します。
func GetRestClient(kubeconfig string) (error, *rest.RESTClient){
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return err, nil
}
config.APIPath = "api"
config.GroupVersion = &corev1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs
restClient, err := rest.RESTClientFor(config)
if err != nil {
return err, nil
}
return nil, restClient
}
これが最初にkubeconfigファイルであり、次にclientcmd.BuildConfigFromFlags( ""、kubeconfig)が構成ファイルをロードして構成オブジェクトを生成します。ClientConfigオブジェクトの生成を確認してください。
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
if kubeconfigPath == "" && masterUrl == "" {
klog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
kubeconfig, err := restclient.InClusterConfig()
if err == nil {
return kubeconfig, nil
}
klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
}
return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
このClientConfig()関数は、Kuebconfigファイルがrestclient.Configオブジェクトにロードされた後、オブジェクトNewNonInteractiveDeferredLoadingClientConfigを変換する役割を果たします。
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
// check that getAuthInfo, getContext, and getCluster do not return an error.
// Do this before checking if the current config is usable in the event that an
// AuthInfo, Context, or Cluster config with user-defined names are not found.
// This provides a user with the immediate cause for error if one is found
configAuthInfo, err := config.getAuthInfo()
if err != nil {
return nil, err
}
_, err = config.getContext()
if err != nil {
return nil, err
}
configClusterInfo, err := config.getCluster()
if err != nil {
return nil, err
}
if err := config.ConfirmUsable(); err != nil {
return nil, err
}
clientConfig := &restclient.Config{}
clientConfig.Host = configClusterInfo.Server
if len(config.overrides.Timeout) > 0 {
timeout, err := ParseTimeout(config.overrides.Timeout)
if err != nil {
return nil, err
}
clientConfig.Timeout = timeout
}
if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
u.RawQuery = ""
u.Fragment = ""
clientConfig.Host = u.String()
}
if len(configAuthInfo.Impersonate) > 0 {
clientConfig.Impersonate = restclient.ImpersonationConfig{
UserName: configAuthInfo.Impersonate,
Groups: configAuthInfo.ImpersonateGroups,
Extra: configAuthInfo.ImpersonateUserExtra,
}
}
// only try to read the auth information if we are secure
if restclient.IsConfigTransportTLS(*clientConfig) {
var err error
var persister restclient.AuthProviderConfigPersister
if config.configAccess != nil {
authInfoName, _ := config.getAuthInfoName()
persister = PersisterForUser(config.configAccess, authInfoName)
}
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
if err != nil {
return nil, err
}
mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
if err != nil {
return nil, err
}
mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
}
return clientConfig, nil
}
clientConfigは、最後に生成された構成オブジェクトです。ここに指定されているkubeconfigファイルのパスを使用して、クライアントセットを初期化します。もちろん、client-goはクラスター内構成を使用してこれらのクライアントを初期化できます。通常、デバッグ時にkubeconfigファイルを使用してデバッグします。
次に、RESTClientクライアントの実装を見て、深く理解しましょう。
まず、RESTClientFor関数の実装を見てみましょう。
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")
}
// 有些配置没给配置,就默认配置下。
qps := config.QPS
if config.QPS == 0.0 {
qps = DefaultQPS
}
burst := config.Burst
if config.Burst == 0 {
burst = DefaultBurst
}
baseURL, versionedAPIPath, err := defaultServerUrlFor(config) // 获取基本的URL和apiPath,这个行程了基础的url,一会跟一下代码
if err != nil {
return nil, err
}
transport, err := TransportFor(config)
if err != nil {
return nil, err
}
var httpClient *http.Client
if transport != http.DefaultTransport {
httpClient = &http.Client{Transport: transport}
if config.Timeout > 0 {//是否设置超时时间,没有就给默认值
httpClient.Timeout = config.Timeout
}
}
return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient) // 新建的NewRESTClient
}
まず、defaultServerUrlFor関数のコードを見てください。
func defaultServerUrlFor(config *Config) (*url.URL, string, error) {
// TODO: move the default to secure when the apiserver supports TLS by default
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
defaultTLS := hasCA || hasCert || config.Insecure
host := config.Host // 拿出hosts
if host == "" {
host = "localhost"
}
if config.GroupVersion != nil { // groupversion
return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS)
}
return DefaultServerURL(host, config.APIPath, schema.GroupVersion{}, defaultTLS)
}
NewRESTClient関数の内容を見てください。
func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConfig, maxQPS float32, maxBurst int, rateLimiter flowcontrol.RateLimiter, client *http.Client) (*RESTClient, error) {
base := *baseURL
if !strings.HasSuffix(base.Path, "/") {
base.Path += "/"
}
base.RawQuery = ""
base.Fragment = ""
if config.GroupVersion == nil {
config.GroupVersion = &schema.GroupVersion{}
}
if len(config.ContentType) == 0 {
config.ContentType = "application/json"
}
serializers, err := createSerializers(config)
if err != nil {
return nil, err
}
var throttle flowcontrol.RateLimiter
if maxQPS > 0 && rateLimiter == nil {
throttle = flowcontrol.NewTokenBucketRateLimiter(maxQPS, maxBurst)
} else if rateLimiter != nil {
throttle = rateLimiter
}
return &RESTClient{
base: &base,
versionedAPIPath: versionedAPIPath,
contentConfig: config,
serializers: *serializers,
createBackoffMgr: readExpBackoffConfig,
Throttle: throttle,
Client: client,
}, nil
}
この関数の読み取りと書き込みは、単なる基本パッケージです。RESTClientを使用してリソースオブジェクトを操作する方法は次のとおりです。
restClient.Get().Namespace("default").Resource("pods").Do()
Get()関数の戻り値は、要求オブジェクトです。
func (c *RESTClient) Get() *Request {
return c.Verb("GET")
}
名前空間関数は、名前空間をリクエストフィールドに設定するだけです。
func (r *Request) Namespace(namespace string) *Request {
if r.err != nil {
return r
}
if r.namespaceSet {
r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace)
return r
}
if msgs := IsValidPathSegmentName(namespace); len(msgs) != 0 {
r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
return r
}
r.namespaceSet = true
r.namespace = namespace // 在request对象中设置namespace
return r
}
リソース機能:
func (r *Request) Resource(resource string) *Request {
if r.err != nil {
return r
}
if len(r.resource) != 0 {
r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource)
return r
}
if msgs := IsValidPathSegmentName(resource); len(msgs) != 0 {
r.err = fmt.Errorf("invalid resource %q: %v", resource, msgs)
return r
}
r.resource = resource // 在request对象中设置resource
return r
}
機能する:
func (r *Request) Do() Result {
r.tryThrottle()
var result Result
err := r.request(func(req *http.Request, resp *http.Response) {
result = r.transformResponse(resp, req)
})
if err != nil {
return Result{err: err}
}
return result
}
内部のリクエスト関数に注目してください。
func (r *Request) request(fn func(*http.Request, *http.Response)) error {
//Metrics for total request latency
start := time.Now()
defer func() {
metrics.RequestLatency.Observe(r.verb, r.finalURLTemplate(), time.Since(start))
}()
if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err)
return r.err
}
// TODO: added to catch programmer errors (invoking operations with an object with an empty namespace)
// 校验参数是否合理
if (r.verb == "GET" || r.verb == "PUT" || r.verb == "DELETE") && r.namespaceSet && len(r.resourceName) > 0 && len(r.namespace) == 0 {
return fmt.Errorf("an empty namespace may not be set when a resource name is provided")
}
if (r.verb == "POST") && r.namespaceSet && len(r.namespace) == 0 {
return fmt.Errorf("an empty namespace may not be set during creation")
}
client := r.client
if client == nil {
client = http.DefaultClient
}
// Right now we make about ten retry attempts if we get a Retry-After response.
maxRetries := 10
retries := 0
for {
url := r.URL().String()// 需要着重看下这个URL函数
req, err := http.NewRequest(r.verb, url, r.body)
if err != nil {
return err
}
if r.timeout > 0 {
if r.ctx == nil {
r.ctx = context.Background()
}
var cancelFn context.CancelFunc
r.ctx, cancelFn = context.WithTimeout(r.ctx, r.timeout)
defer cancelFn()
}
if r.ctx != nil {
req = req.WithContext(r.ctx)
}
req.Header = r.headers
r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL()))
if retries > 0 {
// We are retrying the request that we already send to apiserver
// at least once before.
// This request should also be throttled with the client-internal throttler.
r.tryThrottle()
}
resp, err := client.Do(req)
updateURLMetrics(r, resp, err)
if err != nil {
r.backoffMgr.UpdateBackoff(r.URL(), err, 0)
} else {
r.backoffMgr.UpdateBackoff(r.URL(), err, resp.StatusCode)
}
if err != nil {
// "Connection reset by peer" is usually a transient error.
// Thus in case of "GET" operations, we simply retry it.
// We are not automatically retrying "write" operations, as
// they are not idempotent.
if !net.IsConnectionReset(err) || r.verb != "GET" {
return err
}
// For the purpose of retry, we set the artificial "retry-after" response.
// TODO: Should we clean the original response if it exists?
resp = &http.Response{
StatusCode: http.StatusInternalServerError,
Header: http.Header{"Retry-After": []string{"1"}},
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
}
done := func() bool {
// Ensure the response body is fully read and closed
// before we reconnect, so that we reuse the same TCP
// connection.
defer func() {
const maxBodySlurpSize = 2 << 10
if resp.ContentLength <= maxBodySlurpSize {
io.Copy(ioutil.Discard, &io.LimitedReader{R: resp.Body, N: maxBodySlurpSize})
}
resp.Body.Close()
}()
retries++
if seconds, wait := checkWait(resp); wait && retries < maxRetries {
if seeker, ok := r.body.(io.Seeker); ok && r.body != nil {
_, err := seeker.Seek(0, 0)
if err != nil {
klog.V(4).Infof("Could not retry request, can't Seek() back to beginning of body for %T", r.body)
fn(req, resp)
return true
}
}
klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url)
r.backoffMgr.Sleep(time.Duration(seconds) * time.Second)
return false
}
fn(req, resp)
return true
}()
if done {
return nil
}
}
}
URLの生成に使用されるr.URL関数に注目してください。ここでは、リクエストで設定されたリソース、名前空間などがURLのスプライスに使用されていることがわかります。
func (r *Request) URL() *url.URL {
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.baseURL != nil {
*finalURL = *r.baseURL
}
finalURL.Path = p
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
}
このセクションを要約します。api-serverは多くのインターフェースを公開し、RESTClientはこれらのapiをカプセル化するため、対応するURLがソースコードで生成されていることがわかります。
ClientSetクライアント
ClientSetクライアントは、デフォルトでk8sの組み込みリソースオブジェクトクライアントのコレクションであり、k8sの組み込みリソースオブジェクトはClientSetクライアントを介して操作できます。以下では、clientsetが組み込みのリソースオブジェクトクライアントのコレクションである理由を紹介します。
コードの一部を紹介しましょう:
func getClientSet(kubeconfig string) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err.Error())
}
// create the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
for {
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
// Examples for error handling:
// - Use helper functions like e.g. errors.IsNotFound()
// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
namespace := "default"
pod := "example-xxxxx"
_, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
if errors.IsNotFound(err) {
fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
fmt.Printf("Error getting pod %s in namespace %s: %v\n",
pod, namespace, statusError.ErrStatus.Message)
} else if err != nil {
panic(err.Error())
} else {
fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
}
time.Sleep(10 * time.Second)
}
}
clientcmd.BuildConfigFromFlags関数は上記で分析されています。これは、さまざまな場所、クライアントセットを生成する関数kubernetes.NewForConfigの分析です。ここでは、csが多くのk8s組み込みリソースクライアントオブジェクトであり、設定Aであることがわかります。 DiscoveryClient:
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
}
var cs Clientset
var err error
// 这边开始会初始化k8s内置的资源的clientset
cs.admissionregistrationV1beta1, err = admissionregistrationv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.appsV1, err = appsv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.appsV1beta1, err = appsv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.appsV1beta2, err = appsv1beta2.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.auditregistrationV1alpha1, err = auditregistrationv1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.authenticationV1, err = authenticationv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.authenticationV1beta1, err = authenticationv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.authorizationV1, err = authorizationv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.authorizationV1beta1, err = authorizationv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.autoscalingV1, err = autoscalingv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.autoscalingV2beta1, err = autoscalingv2beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.autoscalingV2beta2, err = autoscalingv2beta2.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.batchV1, err = batchv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.batchV1beta1, err = batchv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.batchV2alpha1, err = batchv2alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.certificatesV1beta1, err = certificatesv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.coordinationV1beta1, err = coordinationv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.coordinationV1, err = coordinationv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.coreV1, err = corev1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.eventsV1beta1, err = eventsv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.extensionsV1beta1, err = extensionsv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.networkingV1, err = networkingv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.networkingV1beta1, err = networkingv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.nodeV1alpha1, err = nodev1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.nodeV1beta1, err = nodev1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.policyV1beta1, err = policyv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.rbacV1, err = rbacv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.rbacV1beta1, err = rbacv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.rbacV1alpha1, err = rbacv1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.schedulingV1alpha1, err = schedulingv1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.schedulingV1beta1, err = schedulingv1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.schedulingV1, err = schedulingv1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.settingsV1alpha1, err = settingsv1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.storageV1beta1, err = storagev1beta1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.storageV1, err = storagev1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.storageV1alpha1, err = storagev1alpha1.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) // 注意啦,clientset里面还放了一个DiscoverClient
if err != nil {
return nil, err
}
return &cs, nil
}
次に、NewForConfigを追跡して確認すると、rest.RESTClientForが表示されたときに、それが何であるかがわかります。この関数は、RESTClientの実装です。
func NewForConfig(c *rest.Config) (*AppsV1beta1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AppsV1beta1Client{client}, nil
}
これで、clientsetクライアントが各k8s組み込みリソースのクライアントオブジェクトのコレクションである理由がわかりました。
DynamicClient
DynamicClientの特徴は、k8sの組み込みリソースを使用するだけでなく、CRDリソースも使用できることです。dynamicClientの原則は、受信リソースデータがmap [string] interface {}構造を使用することです。
func getDynamicClientExample(kubeconfig string) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err)
}
client, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
deployment := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "demo-deployment",
},
"spec": map[string]interface{}{
"replicas": 2,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "demo",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "demo",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
{
"name": "web",
"image": "nginx:1.12",
"ports": []map[string]interface{}{
{
"name": "http",
"protocol": "TCP",
"containerPort": 80,
},
},
},
},
},
},
},
},
}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := client.Resource(deploymentRes).Namespace("").Create(deployment, metav1.CreateOptions{})
if err != nil {
panic(err)
}
fmt.Printf("Created deployment %q.\n", result.GetName())
}
dynamic.NewForConfig(config)から直接開始します。
func NewForConfig(inConfig *rest.Config) (Interface, error) {
config := rest.CopyConfig(inConfig)
// for serializing the options
config.GroupVersion = &schema.GroupVersion{}
config.APIPath = "/if-you-see-this-search-for-the-break"
config.AcceptContentTypes = "application/json"
config.ContentType = "application/json"
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
restClient, err := rest.RESTClientFor(config) //还是去新建了一个RESTClient客户端
if err != nil {
return nil, err
}
return &dynamicClient{client: restClient}, nil
}
コードから、dynamicClientはまだrestClientです。
DiscoveryClient
DiscoveryClientは、主にk8s api-serverでサポートされているリソースグループ、リソースバージョン、およびリソース情報を検出するために使用される検出クライアントです。
最初にダモが来る:
func getDisCoveryClient(kubeconfig string) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err)
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
panic(err)
}
_, APIResourceList, err := discoveryClient.ServerGroupsAndResources()\
if err != nil {
panic(err)
}
for _, list := range APIResourceList {
}
}
この関数discovery.NewDiscoveryClientForConfig(config)を直接見てください。以下では、RESTClientが使用されていることもわかります。
func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) {
config := *c
// 设置一些默认的参数
if err := setDiscoveryDefaults(&config); err != nil {
return nil, err
}
// 类似RESTClient客户端
client, err := restclient.UnversionedRESTClientFor(&config)
return &DiscoveryClient{restClient: client, LegacyPrefix: "/api"}, err
}
疑問に思う
質問:4種類のクライアントは実際には基本的なクライアントであるrestClientを使用していますが、これを明確に区別する必要がありますか?4種類のクライアントが同時に存在する必要がありますか?
回答:RESTClientで十分である限り、少なくともclientSetとDynamicClientの両方を用意する必要はないと感じました。これは、clientsetクライアントがコード生成を通じてCRDリソースを操作することもできるためです。よく考えてみると、未知のオブジェクトを取得する場合に、dynamicClientクライアントが役立ちます。つまり、clientsetクライアントは、知っているオブジェクトの種類にのみ役立ち、知らない場合にのみ使用できます。 type。dynamicClient。
総括する
client-goクライアントは、クライアントがk8sリソースにアクセスするためのRESTFul APIパッケージであり、ユーザーがk8sを操作しやすくし、コードを理解しやすくします。もちろん、client-goはユーザーにはるかに多くのものを提供します。このセクションでは、主に4種類のクライアントを紹介します。より一般的に使用されるのは、k8sの組み込みリソースを直接操作できるクライアントセットです。client-goのgithubリポジトリに公式の例があります(https://github.com/kubernetes/client-go/blob/master/examples/in-cluster-client-configuration/main.go)
後で、この公式アカウント( "Cloud Native Notes")は、client-goソースコード分析に関する一連の記事を引き続き公開しますので、ご期待ください。