-
Notifications
You must be signed in to change notification settings - Fork 263
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into kn-plugins4
- Loading branch information
Showing
12 changed files
with
547 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
# Client Conventions | ||
|
||
This document describes conventions that Knative domain-specific clients can | ||
follow to achieve specific end-user goals. It is intended as a set of best | ||
practices for client implementers, and also as advice to direct users of the API | ||
(for example, with kubectl). | ||
|
||
These conventions are merely conventions: | ||
|
||
- They are optional; you can use Knative entirely validly without them. | ||
- They are designed to be useful even when some clients are not obeying the | ||
conventions. Each convention describes what happens in the presence of | ||
convention-unaware clients. | ||
|
||
Some of the conventions involve the client setting labels or annotations; the | ||
`client.knative.dev/*` label/annotation namespace is reserved for documented | ||
Knative client conventions. | ||
|
||
## Determine when an action is complete | ||
|
||
As Knative is (like all of Kubernetes) a declarative API, the user expresses | ||
their desire by changing some values in the Knative objects. Clients need not be | ||
declarative, and might have expressions of user intent like "Deploy this code" | ||
or "Change these environment variables". To tell when such an action is | ||
complete, the client can look at the status conditions. | ||
|
||
Each Knative object has a `Ready` status condition. When a change is initiated, | ||
the controller flips this to `Unknown`. When the serving state again reflects | ||
exactly what the spec of the object specifies, the `Ready` condition will flip | ||
to `True`; this indicates the operation was a success. If reflecting the spec in | ||
the serving state is impossible, the `Ready` condition will flip to `False`; | ||
this indicates the operation was a failure, and the message of the status | ||
condition should indicate something in English about why (and the Reason field | ||
can indicate an enumeration suitable for i18n). Either `True` or `False` | ||
indicates the operation is complete, for better or worse. | ||
|
||
Note that someone else could start another operation while the client was | ||
waiting for its operation. A conventional client still waits for the `Ready` | ||
condition to land at `True` or `False`, and then describes to the user what | ||
happened using logic based on the intended effect. | ||
|
||
For example: | ||
|
||
- Client A deploys image `gcr.io/foods/vegetables:eggplant` | ||
- While that is not yet Ready, client B deploys `gcr.io/foods/vegetables:squash` | ||
- The `eggplant` revision becomes Ready: True, and the service moves traffic to | ||
it. (NB: implementations may choose not to move traffic to any but the latest | ||
revision.) | ||
- The `squash` revision fails to bind to a port, and becomes Ready: False | ||
- The Service switches from Ready: Unknown to Ready: False because `squash` | ||
failed. | ||
|
||
Both client A and B should wait for the last step in this procedure. | ||
|
||
- Client A sees that `latestReadyRevisionName` is the revision with the | ||
[nonce](#associate-modifications-with-revisions) it specified, and that | ||
`latestCreatedRevisionName` is not. It tells the user that deploying was | ||
successful. | ||
- Client B sees that `latestCreatedRevisionName` is the revision with the nonce | ||
it specified; it reports the failure with the appropriate message. | ||
|
||
The rule is "Wait for `Ready` to become `True` or `False`, then report on | ||
whether your intent was accomplished". The `Ready` success or failure can be | ||
part of this report, but may be confusing (as in the example) if it's the only | ||
thing you report. | ||
|
||
## Associate modifications with Revisions | ||
|
||
Every time the client changes a Service or Configuration in a way that results | ||
in a new Revision, it may change the `name` in the `ObjectMeta` of the revision | ||
template to a new value, chosen to include either a new random value or one more | ||
than the current generation of the Service or Configuration object. | ||
|
||
This way, the client can get a particular revision by name to find the Revision | ||
the particular change generated. The client can use that revision to, for | ||
example, inform the user about the readiness of their requested change, or to | ||
find the digest of the resolved image for the revision. | ||
|
||
### In the presence of non-conventional clients | ||
|
||
If an client does not set the revision name, the client may find the | ||
`status.latestCreatedRevision` field useful, even though using it is subject to | ||
a race condition, if the client compares the relevant informatin on the found | ||
revision to the template. For example, if the image on the template matches the | ||
`latestCreatedRevision`'s image, the client is justified in using the | ||
`status.imageDigest` field from the revision. | ||
|
||
## Force creation of a new Revision | ||
|
||
The way to deploy new code with a previously-used tag is to make a new Revision, | ||
which the Revision controller will re-pull and lock it to the current image at | ||
that tag. Since Knative is a declarative API, it requires some change to the | ||
desired state of the world (the spec) to trigger any change. | ||
|
||
A client-provided revision name can help in forcing the creation of a new | ||
Revision; if the name is changed, the Configuration controller must make a new | ||
Revision even if nothing else has changed. | ||
|
||
Example: | ||
|
||
```yaml | ||
apiVersion: serving.knative.dev/v1alpha1 | ||
kind: Configuration | ||
metadata: | ||
name: my-service # Named the same as the Service | ||
spec: | ||
template: # template for building Revision | ||
metadata: | ||
name: my-service-dad00dab1de5 | ||
spec: | ||
container: | ||
image: gcr.io/... # new image | ||
``` | ||
## Change non-code attributes | ||
When the user specifies they'd like to change an environment variable (or a | ||
memory allocation, or a concurrency setting...), and does not specify that | ||
they'd like to deploy a change in code, the user would be quite surprised to | ||
find the newest image at their deployed tag running in the cloud. | ||
### General idea | ||
Since the Revision controller will resolve an image tag for every Revision | ||
creation, we need a way to express a non-code change. Clients should do this by | ||
changing the `image` field to be a digest-based image URL supplied by the | ||
Revision `status.imageDigest` field, while marking the original tag-based user | ||
intent in an annotation. | ||
|
||
### Procedure | ||
|
||
1. Get the current state of the Service in question. | ||
2. Get a **base revision**, the Revision corresponding to the fetched state of | ||
the Service: If the template's `metadata.name` is set, get that revision. If | ||
not, fetch the `latestCreatedRevisionName` from the status, and uses that as | ||
the base revision. | ||
3. Copy the `status.imageDigest` field from the base revision into the `image` | ||
field of the Service. This ensures the running code stays the same. | ||
4. Make whatever other modifications to the Service. | ||
5. Add the `client.knative.dev/user-image` annotation to the Service, | ||
containing the original tag-based URL of the image. | ||
6. Set the `metadata.name` on the template to a new unique name value. | ||
7. Post the resulting Service to create a new Revision. | ||
|
||
### Changing code | ||
|
||
When clients do want to change code, they can either require the user to specify | ||
an image (which they put into the `image` field), or implement a "update the | ||
code to whatever's at your previously-deployed tag" operation which copies the | ||
`client.knative.dev/user-image` annotation back to `image`. | ||
|
||
### Display images | ||
|
||
Since we're now filling in the `image` field with a URL the user may never have | ||
specified by hand, a client can display the image for human-readability as the | ||
contents of the `client.knative.dev/user-image` annotation, combined with the | ||
note that it is "at digest <digest>", fetched from the `imageDigest` of the | ||
revision (or the `image` field itself of the Service). | ||
|
||
For example, the displayed value for the image may be the same when: | ||
|
||
- `container.image` is `gcr.io/veggies/eggplant:purple` and `status.imageDigest` | ||
of the relevant revision is `gcr.io/veggies/eggplant@sha256:45b23dee08af...` | ||
- `container.image` is `gcr.io/veggies/eggplant@sha256:45b23dee08af...` and the | ||
`client.knative.dev/user-image` annotation is `gcr.io/veggies/eggplant:purple` | ||
|
||
In both cases the client may tell the user the image is | ||
"`gcr.io/veggies/eggplant:purple` at `sha256:45b23dee...`" | ||
|
||
### In the presence of non-conventional clients | ||
|
||
Non-convention-following clients can mess with this in the following ways: | ||
|
||
- Not set a revision name. | ||
- In this case, we fall back to using the race-prone | ||
`latestCreatedRevisionName` field to determine the base revision. This will | ||
be almost-always correct, but may sometimes result in a situation where an | ||
unaware client changing code and a well-behaved client changing | ||
configuration race with each other, and the code change is not reflected in | ||
the revision that becomes live. | ||
- Not set the user-image annotation. | ||
- Clients should display the contents of the `image` field if the `user-image` | ||
annotation is unspecified or implausible (an implausible value is one that | ||
does not share the same path prefix before the sha/tag). | ||
- Attempt to deploy new code by changing something other than `image`. This will | ||
not work once a conventional client changes it to a digest. All clients should | ||
not assume that new code will be deployed unless they make the `image` field | ||
be their desired code _and_ change something about the `template`. | ||
|
||
Furthermore, _before_ a user has used a well-behaved client to change an env var | ||
or something, using an unaware client like kubectl to change an env var will | ||
re-resolve the image if the user deployed an image by tag. (This would only be | ||
avoidable if the server were to create new by-digest revisions for the user.) | ||
After the user uses a well-behaved client, the image is by-digest anyway so | ||
using kubectl won't mess anything up. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.