mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			346 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| <!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
 | |
| 
 | |
| <!-- BEGIN STRIP_FOR_RELEASE -->
 | |
| 
 | |
| <img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | |
|      width="25" height="25">
 | |
| <img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | |
|      width="25" height="25">
 | |
| <img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | |
|      width="25" height="25">
 | |
| <img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | |
|      width="25" height="25">
 | |
| <img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | |
|      width="25" height="25">
 | |
| 
 | |
| <h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
 | |
| 
 | |
| If you are using a released version of Kubernetes, you should
 | |
| refer to the docs that go with that version.
 | |
| 
 | |
| Documentation for other releases can be found at
 | |
| [releases.k8s.io](http://releases.k8s.io).
 | |
| </strong>
 | |
| --
 | |
| 
 | |
| <!-- END STRIP_FOR_RELEASE -->
 | |
| 
 | |
| <!-- END MUNGE: UNVERSIONED_WARNING -->
 | |
| 
 | |
| <!-- BEGIN MUNGE: GENERATED_TOC -->
 | |
| 
 | |
| - [Client: layering and package structure](#client-layering-and-package-structure)
 | |
|   - [Desired layers](#desired-layers)
 | |
|     - [Transport](#transport)
 | |
|     - [RESTClient/request.go](#restclientrequestgo)
 | |
|     - [Mux layer](#mux-layer)
 | |
|     - [High-level: Individual typed](#high-level-individual-typed)
 | |
|       - [High-level, typed: Discovery](#high-level-typed-discovery)
 | |
|     - [High-level: Dynamic](#high-level-dynamic)
 | |
|     - [High-level: Client Sets](#high-level-client-sets)
 | |
|   - [Package Structure](#package-structure)
 | |
|   - [Client Guarantees (and testing)](#client-guarantees-and-testing)
 | |
| 
 | |
| <!-- END MUNGE: GENERATED_TOC -->
 | |
| 
 | |
| # Client: layering and package structure
 | |
| 
 | |
| ## Desired layers
 | |
| 
 | |
| ### Transport
 | |
| 
 | |
| The transport layer is concerned with round-tripping requests to an apiserver
 | |
| somewhere. It consumes a Config object with options appropriate for this.
 | |
| (That's most of the current client.Config structure.)
 | |
| 
 | |
| Transport delivers an object that implements http's RoundTripper interface
 | |
| and/or can be used in place of http.DefaultTransport to route requests.
 | |
| 
 | |
| Transport objects are safe for concurrent use, and are cached and reused by
 | |
| subsequent layers.
 | |
| 
 | |
| Tentative name: "Transport".
 | |
| 
 | |
| It's expected that the transport config will be general enough that third
 | |
| parties (e.g., OpenShift) will not need their own implementation, rather they
 | |
| can change the certs, token, etc., to be appropriate for their own servers,
 | |
| etc..
 | |
| 
 | |
| Action items:
 | |
| * Split out of current client package into a new package. (@krousey)
 | |
| 
 | |
| ### RESTClient/request.go
 | |
| 
 | |
| RESTClient consumes a Transport and a Codec (and optionally a group/version),
 | |
| and produces something that implements the interface currently in request.go.
 | |
| That is, with a RESTClient, you can write chains of calls like:
 | |
| 
 | |
| `c.Get().Path(p).Param("name", "value").Do()`
 | |
| 
 | |
| RESTClient is generically usable by any client for servers exposing REST-like
 | |
| semantics. It provides helpers that benefit those following api-conventions.md,
 | |
| but does not mandate them. It provides a higher level http interface that
 | |
| abstracts transport, wire serialization, retry logic, and error handling.
 | |
| Kubernetes-like constructs that deviate from standard HTTP should be bypassable.
 | |
| Every non-trivial call made to a remote restful API from Kubernetes code should
 | |
| go through a rest client.
 | |
| 
 | |
| The group and version may be empty when constructing a RESTClient. This is valid
 | |
| for executing discovery commands. The group and version may be overridable with
 | |
| a chained function call.
 | |
| 
 | |
| Ideally, no semantic behavior is built into RESTClient, and RESTClient will use
 | |
| the Codec it was constructed with for all semantic operations, including turning
 | |
| options objects into URL query parameters. Unfortunately, that is not true of
 | |
| today's RESTClient, which may have some semantic information built in. We will
 | |
| remove this.
 | |
| 
 | |
| RESTClient should not make assumptions about the format of data produced or
 | |
| consumed by the Codec. Currently, it is JSON, but we want to support binary
 | |
| protocols in the future.
 | |
| 
 | |
| The Codec would look something like this:
 | |
| 
 | |
| ```go
 | |
| type Codec interface {
 | |
|   Encode(runtime.Object) ([]byte, error)
 | |
|   Decode([]byte]) (runtime.Object, error)
 | |
| 
 | |
|   // Used to version-control query parameters
 | |
|   EncodeParameters(optionsObject runtime.Object) (url.Values, error)
 | |
| 
 | |
|   // Not included here since the client doesn't need it, but a corresponding
 | |
|   // DecodeParametersInto method would be available on the server.
 | |
| }
 | |
| ```
 | |
| 
 | |
| There should be one codec per version. RESTClient is *not* responsible for
 | |
| converting between versions; if a client wishes, they can supply a Codec that
 | |
| does that. But RESTClient will make the assumption that it's talking to a single
 | |
| group/version, and will not contain any conversion logic. (This is a slight
 | |
| change from the current state.)
 | |
| 
 | |
| As with Transport, it is expected that 3rd party providers following the api
 | |
| conventions should be able to use RESTClient, and will not need to implement
 | |
| their own.
 | |
| 
 | |
| Action items:
 | |
| * Split out of the current client package. (@krousey)
 | |
| * Possibly, convert to an interface (currently, it's a struct). This will allow
 | |
|   extending the error-checking monad that's currently in request.go up an
 | |
|   additional layer.
 | |
| * Switch from ParamX("x") functions to using types representing the collection
 | |
|   of parameters and the Codec for query parameter serialization.
 | |
| * Any other Kubernetes group specific behavior should also be removed from
 | |
|   RESTClient.
 | |
| 
 | |
| ### Mux layer
 | |
| 
 | |
| (See TODO at end; this can probably be merged with the "client set" concept.)
 | |
| 
 | |
| The client muxer layer has a map of group/version to cached RESTClient, and
 | |
| knows how to construct a new RESTClient in case of a cache miss (using the
 | |
| discovery client mentioned below). The ClientMux may need to deal with multiple
 | |
| transports pointing at differing destinations (e.g. OpenShift or other 3rd party
 | |
| provider API may be at a different location).
 | |
| 
 | |
| When constructing a RESTClient generically, the muxer will just use the Codec
 | |
| the high-level dynamic client would use. Alternatively, the user should be able
 | |
| to pass in a Codec-- for the case where the correct types are compiled in.
 | |
| 
 | |
| Tentative name: ClientMux
 | |
| 
 | |
| Action items:
 | |
| * Move client cache out of kubectl libraries into a more general home.
 | |
| * TODO: a mux layer may not be necessary, depending on what needs to be cached.
 | |
|   If transports are cached already, and RESTClients are extremely light-weight,
 | |
|   there may not need to be much code at all in this layer.
 | |
| 
 | |
| ### High-level: Individual typed
 | |
| 
 | |
| Our current high-level client allows you to write things like
 | |
| `c.Pods("namespace").Create(p)`; we will insert a level for the group.
 | |
| 
 | |
| That is, the system will be:
 | |
| 
 | |
| `clientset.GroupName().NamespaceSpecifier().Action()`
 | |
| 
 | |
| Where:
 | |
| * `clientset` is a thing that holds multiple individually typed clients (see
 | |
|   below).
 | |
| * `GroupName()` returns the generated client that this section is about.
 | |
| * `NamespaceSpecifier()` may take a namespace parameter or nothing.
 | |
| * `Action` is one of Create/Get/Update/Delete/Watch, or appropriate actions
 | |
|   from the type's subresources.
 | |
| * It is TBD how we'll represent subresources and their actions. This is
 | |
|   inconsistent in the current clients, so we'll need to define a consistent
 | |
|   format. Possible choices:
 | |
|  * Insert a `.Subresource()` before the `.Action()`
 | |
|  * Flatten subresources, such that they become special Actions on the parent
 | |
|    resource.
 | |
| 
 | |
| The types returned/consumed by such functions will be e.g. api/v1, NOT the
 | |
| current version inspecific types. The current internal-versioned client is
 | |
| inconvenient for users, as it does not protect them from having to recompile
 | |
| their code with every minor update. (We may continue to generate an
 | |
| internal-versioned client for our own use for a while, but even for our own
 | |
| components it probably makes sense to switch to specifically versioned clients.)
 | |
| 
 | |
| We will provide this structure for each version of each group. It is infeasible
 | |
| to do this manually, so we will generate this. The generator will accept both
 | |
| swagger and the ordinary go types. The generator should operate on out-of-tree
 | |
| sources AND out-of-tree destinations, so it will be useful for consuming
 | |
| out-of-tree APIs and for others to build custom clients into their own
 | |
| repositories.
 | |
| 
 | |
| Typed clients will be constructabale given a ClientMux; the typed constructor will use
 | |
| the ClientMux to find or construct an appropriate RESTClient. Alternatively, a
 | |
| typed client should be constructable individually given a config, from which it
 | |
| will be able to construct the appropriate RESTClient.
 | |
| 
 | |
| Typed clients do not require any version negotiation. The server either supports
 | |
| the client's group/version, or it does not. However, there are ways around this:
 | |
| * If you want to use a typed client against a server's API endpoint and the
 | |
|   server's API version doesn't match the client's API version, you can construct
 | |
|   the client with a RESTClient using a Codec that does the conversion (this is
 | |
|   basically what our client does now).
 | |
| * Alternatively, you could use the dynamic client.
 | |
| 
 | |
| Action items:
 | |
| * Move current typed clients into new directory structure (described below)
 | |
| * Finish client generation logic. (@caesarxuchao, @lavalamp)
 | |
| 
 | |
| #### High-level, typed: Discovery
 | |
| 
 | |
| A `DiscoveryClient` is necessary to discover the api groups, versions, and
 | |
| resources a server supports. It's constructable given a RESTClient. It is
 | |
| consumed by both the ClientMux and users who want to iterate over groups,
 | |
| versions, or resources. (Example: namespace controller.)
 | |
| 
 | |
| The DiscoveryClient is *not* required if you already know the group/version of
 | |
| the resource you want to use: you can simply try the operation without checking
 | |
| first, which is lower-latency anyway as it avoids an extra round-trip.
 | |
| 
 | |
| Action items:
 | |
| * Refactor existing functions to present a sane interface, as close to that
 | |
|   offered by the other typed clients as possible. (@caeserxuchao)
 | |
| * Use a RESTClient to make the necessary API calls.
 | |
| * Make sure that no discovery happens unless it is explicitly requested. (Make
 | |
|   sure SetKubeDefaults doesn't call it, for example.)
 | |
| 
 | |
| ### High-level: Dynamic
 | |
| 
 | |
| The dynamic client lets users consume apis which are not compiled into their
 | |
| binary. It will provide the same interface as the typed client, but will take
 | |
| and return `runtime.Object`s instead of typed objects. There is only one dynamic
 | |
| client, so it's not necessary to generate it, although optionally we may do so
 | |
| depending on whether the typed client generator makes it easy.
 | |
| 
 | |
| A dynamic client is constructable given a config, group, and version. It will
 | |
| use this to construct a RESTClient with a Codec which encodes/decodes to
 | |
| 'Unstructured' `runtime.Object`s. The group and version may be from a previous
 | |
| invocation of a DiscoveryClient, or they may be known by other means.
 | |
| 
 | |
| For now, the dynamic client will assume that a JSON encoding is allowed. In the
 | |
| future, if we have binary-only APIs (unlikely?), we can add that to the
 | |
| discovery information and construct an appropriate dynamic Codec.
 | |
| 
 | |
| Action items:
 | |
| * A rudimentary version of this exists in kubectl's builder. It needs to be
 | |
|   moved to a more general place.
 | |
| * Produce a useful 'Unstructured' runtime.Object, which allows for easy
 | |
|   Object/ListMeta introspection.
 | |
| 
 | |
| ### High-level: Client Sets
 | |
| 
 | |
| Because there will be multiple groups with multiple versions, we will provide an
 | |
| aggregation layer that combines multiple typed clients in a single object.
 | |
| 
 | |
| We do this to:
 | |
| * Deliver a concrete thing for users to consume, construct, and pass around. We
 | |
|   don't want people making 10 typed clients and making a random system to keep
 | |
|   track of them.
 | |
| * Constrain the testing matrix. Users can generate a client set at their whim
 | |
|   against their cluster, but we need to make guarantees that the clients we
 | |
|   shipped with v1.X.0 will work with v1.X+1.0, and vice versa. That's not
 | |
|   practical unless we "bless" a particular version of each API group and ship an
 | |
|   official client set with earch release. (If the server supports 15 groups with
 | |
|   2 versions each, that's 2^15 different possible client sets. We don't want to
 | |
|   test all of them.)
 | |
| 
 | |
| A client set is generated into its own package. The generator will take the list
 | |
| of group/versions to be included. Only one version from each group will be in
 | |
| the client set.
 | |
| 
 | |
| A client set is constructable at runtime from either a ClientMux or a transport
 | |
| config (for easy one-stop-shopping).
 | |
| 
 | |
| An example:
 | |
| 
 | |
| ```go
 | |
| import (
 | |
|   api_v1 "k8s.io/kubernetes/pkg/client/typed/generated/v1"
 | |
|   ext_v1beta1 "k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1"
 | |
|   net_v1beta1 "k8s.io/kubernetes/pkg/client/typed/generated/net/v1beta1"
 | |
|   "k8s.io/kubernetes/pkg/client/typed/dynamic"
 | |
| )
 | |
| 
 | |
| type Client interface {
 | |
|   API() api_v1.Client
 | |
|   Extensions() ext_v1beta1.Client
 | |
|   Net() net_v1beta1.Client
 | |
|   // ... other typed clients here.
 | |
| 
 | |
|   // Included in every set
 | |
|   Discovery() discovery.Client
 | |
|   GroupVersion(group, version string) dynamic.Client
 | |
| }
 | |
| ```
 | |
| 
 | |
| Note that a particular version is chosen for each group. It is a general rule
 | |
| for our API structure that no client need care about more than one version of
 | |
| each group at a time.
 | |
| 
 | |
| This is the primary deliverable that people would consume. It is also generated.
 | |
| 
 | |
| Action items:
 | |
| * This needs to be built. It will replace the ClientInterface that everyone
 | |
|   passes around right now.
 | |
| 
 | |
| ## Package Structure
 | |
| 
 | |
| ```
 | |
| pkg/client/
 | |
| ----------/transport/     # transport & associated config
 | |
| ----------/restclient/
 | |
| ----------/clientmux/
 | |
| ----------/typed/
 | |
| ----------------/discovery/
 | |
| ----------------/generated/
 | |
| --------------------------/<group>/
 | |
| ----------------------------------/<version>/
 | |
| --------------------------------------------/<resource>.go
 | |
| ----------------/dynamic/
 | |
| ----------/clientsets/
 | |
| ---------------------/release-1.1/
 | |
| ---------------------/release-1.2/
 | |
| ---------------------/the-test-set-you-just-generated/
 | |
| ```
 | |
| 
 | |
| `/clientsets/` will retain their contents until they reach their expire date.
 | |
| e.g., when we release v1.N, we'll remove clientset v1.(N-3). Clients from old
 | |
| releases live on and continue to work (i.e., are tested) without any interface
 | |
| changes for multiple releases, to give users time to transition.
 | |
| 
 | |
| ## Client Guarantees (and testing)
 | |
| 
 | |
| Once we release a clientset, we will not make interface changes to it. Users of
 | |
| that client will not have to change their code until they are deliberately
 | |
| upgrading their import. We probably will want to generate some sort of stub test
 | |
| with a clienset, to ensure that we don't change the interface.
 | |
| 
 | |
| 
 | |
| <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | |
| []()
 | |
| <!-- END MUNGE: GENERATED_ANALYTICS -->
 |