mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 04:06:03 +00:00
Merge pull request #15730 from lavalamp/client-doc
Auto commit by PR queue bot
This commit is contained in:
commit
598e3b0c17
349
docs/proposals/client-package-structure.md
Normal file
349
docs/proposals/client-package-structure.md
Normal file
@ -0,0 +1,349 @@
|
||||
<!-- 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.
|
||||
|
||||
<strong>
|
||||
The latest release of this document can be found
|
||||
[here](http://releases.k8s.io/release-1.1/docs/proposals/client-package-structure.md).
|
||||
|
||||
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 -->
|
Loading…
Reference in New Issue
Block a user