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.
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 it specified, and thatlatestCreatedRevisionName
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.
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.
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 information 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.
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:
apiVersion: serving.knative.dev/v1
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
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.
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.
- Get the current state of the Service in question.
- 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 thelatestCreatedRevisionName
from the status, and uses that as the base revision. - Copy the
status.imageDigest
field from the base revision into theimage
field of the Service. This ensures the running code stays the same. - Make whatever other modifications to the Service.
- Add the
client.knative.dev/user-image
annotation to the Service, containing the original tag-based URL of the image. - Set the
metadata.name
on the template to a new unique name value. - Post the resulting Service to create a new Revision.
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
.
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 ", 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
isgcr.io/veggies/eggplant:purple
andstatus.imageDigest
of the relevant revision isgcr.io/veggies/eggplant@sha256:45b23dee08af...
container.image
isgcr.io/veggies/eggplant@sha256:45b23dee08af...
and theclient.knative.dev/user-image
annotation isgcr.io/veggies/eggplant:purple
In both cases the client may tell the user the image is
"gcr.io/veggies/eggplant:purple
at sha256:45b23dee...
"
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.
- In this case, we fall back to using the race-prone
- Not set the user-image annotation.
- Clients should display the contents of the
image
field if theuser-image
annotation is unspecified or implausible (an implausible value is one that does not share the same path prefix before the sha/tag).
- Clients should display the contents of the
- 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 theimage
field be their desired code and change something about thetemplate
.
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.