Closed resouer closed 4 years ago
/cc @ryanzhang-oss @negz @vturecek @artursouza @hongchaodeng @wonderflow
The experience of modifying Component name for every update has been proven as unacceptable. The Component shouldn't become a "template" object.
Totally agree.
This point is really very IMPORTANT and we MUST fix it ASAP.
This sounds a great idea!
Component should capture all developers' needs and should be modifiable by developers. The revisions should be managed by OAM platform and used by developers to rollback or canary rollout with ease.
Per discussion with @vturecek, it seems that we all agreed Component should become mutable in general.
Though Vac proposed "Manual Gate" before updated really happen instead of "Track Latest" mentioned in previous proposal:
AppConfig still refer the name of Component which implies its controller always tracks the latest version of Component (i.e. the latest desired state of Workload).
"Manual Gate" means after a Component is modified, AppConfig controller will only generate the Workload CR when a revision
field is manually updated to point to the newer version in AppConfig.
"Manual Gate" is useful, though the main argument is around default behavior, there're two approaches:
Approach 1: Track Latest by default.
Solution 1: AppConfig have revision
field but it's optional.
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
meta:
name: my-app
spec:
components:
- component: frontend
revision: 1 # Manual Gate
- component: backend # Track Latest
Solution 2: no revision
field in AppConfig, and rely on a core Trait to control the upgrade behavior (i.e. Manual Gate per Ops requirement).
---
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
meta:
name: my-app
spec:
components:
- component: frontend
traits:
- trait:
apiVersion: core.oam.dev/v1alpha2
kind: ManualGate
spec:
revision: 1
- component: backend # Track Latest
Approach 2: Manual Gate by default.
Solution: revision
field is required in AppConfig, and revision: latest
means track latest.
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
meta:
name: my-app
spec:
components:
- component: frontend
revision: 1 # Manual Gate
- component: backend
revision: latest # Track Latest
In general, Approach 1 has better UI/UX, more flexibility and doesn't need to change Spec a lot, though Approach 2 has stronger workflow control.
I like approach 2 better because it looks familiar to users from Docker compose.
According to discussion in community meeting (4/21), we agree on Approach 1 (Track Latest by default) for better user experience and backward compatibility.
Though there's argument raised about how Revision
will be designed. It's off topic for this issue while some details could be copied and highlighted to here from #336.
Solution: introducing an independent Revision
object to track Component change automatically.
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: frontend
spec:
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
metadata:
name: sample-workload
spec:
containers:
- name: my-cool-workload
image: example/very-cool-workload:0.1.2@sha256:verytrustworthyhash
cmd:
- "bash lscpu"
$ kubectl apply -f component.yaml
Let's use ControllerRevision
in K8s as the Revision object in following demo.
A ControllerRevision
object will be generated automatically:
$ kubectl get controllerrevisions
NAMESPACE NAME CONTROLLER REVISION AGE
default frontend-c8bb659c5 core.oam.dev/component 1 2d15h
$ kubectl get controllerrevision frontend-c8bb659c5 -o yaml
apiVersion: apps/v1
kind: ControllerRevision
metadata:
creationTimestamp: "2020-04-19T02:03:27Z"
labels:
controller-revision-hash: c8bb659c5
name: frontend-c8bb659c5
namespace: default
ownerReferences:
- apiVersion: core.oam.dev/v1alpha2
blockOwnerDeletion: true
controller: true
kind: Component
name: frontend
resourceVersion: "338"
revision: 1
data:
spec:
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
metadata:
name: sample-workload
spec:
containers:
- name: my-cool-workload
image: example/very-cool-workload:0.1.2@sha256:verytrustworthyhash
cmd:
- "bash lscpu"
Make a change to component.yaml
:
cmd:
- - "bash lscpu"
+ - "bash top"
A new ControllerRevision
will be automatically generated:
$ kubectl get controllerrevisions
NAMESPACE NAME CONTROLLER REVISION AGE
default frontend-c8bb659c5 core.oam.dev/component 1 2d15h
default frontend-a75588698 core.oam.dev/component 2 2d14h
$ kubectl get controllerrevision frontend-a75588698 -o yaml
apiVersion: apps/v1
kind: ControllerRevision
metadata:
creationTimestamp: "2020-04-19T03:03:27Z"
labels:
controller-revision-hash: a75588698
name: frontend-a75588698
namespace: default
ownerReferences:
- apiVersion: core.oam.dev/v1alpha2
blockOwnerDeletion: true
controller: true
kind: Component
name: frontend
resourceVersion: "339"
revision: 2
data:
spec:
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
metadata:
name: sample-workload
spec:
containers:
- name: my-cool-workload
image: example/very-cool-workload:0.1.2@sha256:verytrustworthyhash
cmd:
- "bash top"
The name of ControllerRevision
could be referenced by Trait in AppConfig:
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: example-appconfig
spec:
components:
- componentName: frontend
traits:
- trait:
apiVersion: core.oam.dev/v1alpha2
kind: FancyTrait
spec:
revision: frontend-c8bb659c5 # or `revision: 1` depends on Trait implementation.
Note that if we allow implementation layer to choose its own Revision
definition (e.g. ControllerRevision
above), we can achieve above workflow without any change to data structures in OAM spec. Otherwise we need to add a ComponentRevision
object in OAM spec.
/cc @artursouza hopes the pseudo workflow above answers your question.
For revision design, there's another alternative to introduce an RevisionTemplate
object in Component
, so developer can explicitly give name to ControllerRevision
instead of always auto-generated.
This is the requirement from @artursouza if I understand correctly.
In that case, the Component
will look like:
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: frontend
spec:
template:
metadata:
name: frontend-v1 # optional, auto generated name by default
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
metadata:
name: sample-workload
spec:
containers:
- name: my-cool-workload
image: example/very-cool-workload:0.1.2@sha256:verytrustworthyhash
cmd:
- "bash lscpu"
Create/update this Component will generate the corresponding ControllerRevision
object we mentioned before:
$ kubectl get controllerrevisions
NAMESPACE NAME CONTROLLER REVISION AGE
default frontend-v1 core.oam.dev/component 1 2d15h
apiVersion: apps/v1
kind: ControllerRevision
metadata:
creationTimestamp: "2020-04-19T02:03:27Z"
labels:
controller-revision-hash: c8bb659c5
name: frontend-v1
namespace: default
ownerReferences:
- apiVersion: core.oam.dev/v1alpha2
blockOwnerDeletion: true
controller: true
kind: Component
name: frontend
resourceVersion: "338"
revision: 1
data:
spec:
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
metadata:
name: sample-workload
spec:
containers:
- name: my-cool-workload
image: example/very-cool-workload:0.1.2@sha256:verytrustworthyhash
cmd:
- "bash lscpu"
Note that if the goal is just control of name, I personally think RevisionTemplate
is not a strong need. We can define a convention that ControllerRevision is always named as component.name + revision number
.
A follow up to https://github.com/oam-dev/spec/issues/350#issuecomment-616952502 :
Since we've brought up Revision design it worth to mention that we can also define component
and revisionName
as exclusive in AppConfig, so user can make choice without thinking about "what's default".
Approach 3: No default.
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
meta:
name: my-app
spec:
components:
- componentName: frontend # Track Latest
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
meta:
name: my-app
spec:
components:
- revisionName: frontend-a75588698 # Manual Gate
If there's no objection, according various feedback from community, it seems Approach 3: No default fits to all need and only introduces minimal change to the spec.
If there's no objection, according various feedback from community, it seems Approach 3: No default fits to all need and only introduces minimal change to the spec.
Agree, is someone going to send a PR for this minor change?
I think the general idea and philosophy described here deserves a design/best practice doc. This would guide the incoming implementation in Crossplane.
Let's pull up a design doc and cover an end2end user scenario on how to do upgrade. It might also include implementation details.
Mutable Component Model
OAM v1alpha1 made a requirement that developer need to define another ComponentSchematic with different name for update (i.e. ComponentSchematic is immutable). We've got many feedbacks from real practices in Alibaba as well as AWS ECS for OAM that it's not a good experience. We agreed on solving it in v1alpha2 with new Component + Workload design. But today, we are still claiming that Component is immutable in v1alpha2 spec.
Goal:
Developer should be able to modify the Component object directly to trigger further upgrade of the application. i.e. Component is mutable.
Non-goal:
Revision design around how to track Component change, this should be addressed in: #336
Proposed Change
Change 1
Remove the assumption that a Component could be shared as "template" across multiple AppConfigs.
Sharing a mutable object is essentially problematic as it mixed ownership from multiple sides. If I defined a Component with
image:v1
, I will be very shocked to see it can be modified by someone else toimage:v2
later.Change 2
The relationship between Component and Workload is not template and instance anymore.
It's important to highlight that what developer maintained is the Component object, it should always represent the desired state of his/her application, and it's an "instance".
Essentially, Component is the envelope to carry Workload with extra fields such as
spec.parameters
(andspec.polices
in the future). Thus Component is 1:1 mapping to Workload CR and they can have same name in implementation.AppConfig still refer the name of Component which implies its controller always tracks the latest version of Component (i.e. the latest desired state of Workload).
Revision consideration
Revision design is off topic but it worth to mention that revisioning could be fully defer to the implementation layer.
In detail, the implementation can choose/define its own
Revision
object internally to record revision history of Component. For example, usingControllerRevision
of Kubernetes to store the Component spec as revision data.The cons is if a Trait needs to explicitly refer revisions (e.g. A/B test trait), it will need to refer to the name of that internal
Revision
object. Of course, this can be fixed by defining aRevision
kind in OAM spec for best portability.Note that developer submitting a Component object only generates a
Revision
object, it does not generate Workload object until AppConfig is submitted (i.e. still lazy instantiate).Alternatives
For the relationship of Component and Workload, there're several other options but seems none of them fully meet our goal.
Option 1:
Component is an envelope to generate Workload which reflects the concept of revision.
That is to say, Component is 1:N mapping to Workload. Every time a developer modified the
spec.workload
of Component, it will generate a Workload object such as:frontend-workload-v1
andfrontend-worklod-v2
, they could be directly referenced as reversion names in Trait.But in this case, upon the developer submitted a Component, the system will have to generate Workload object immediately and this will trigger the Workload controller to response. Otherwise, the operator can't know the name of specific Workload in AppConfig later if he need to refer to a specific revision of Component. This breaks the fundamental workflow unless we enforce convention of Workload names like always end with
-v{number}
suffix. The mind burden is relatively heavy for users though.Note that there're some PaaS/Serveless projects follow this model, because it can rely on its own Workload controller to determine when to generate the real K8s object (e.g. Deployment) after Workload CR is created. But this does not work for us as Workload controller in OAM could be brought by user.
Option 2:
Add a
version
field to Component object, so every time a Component is modified, it generates a new version of Component with same name. AppConfig will need to reference Component byname
+version
.But in Kubernetes resource model which OAM adopted, it's infeasible as you can't have two objects with same name and same G/V/K. Also, it still have same issue of "shared Component".
Conclusion
The experience of modifying Component name for every update has been proven as unacceptable, and we all agree to fix it. But it does not mean the Component should become a "template" object. In practice, it's impossible to maintain such a shared and user facing object in the system. That experience will be broken.
Hence, this proposal is simple in general, we should claim:
Changes needed: