A trait is a discretionary runtime overlay that augments a component workload type (workloadType
) with additional features. It is an opportunity for those in the application operator role to make specific decisions about the configuration of components, but without involving the developer. A trait can be any configuration of a distributed application that applies to an individual component, such as traffic routing rules (e.g., load balancing policy, network ingress routing, circuit breaking, rate limiting), auto-scaling policies, upgrade strategies, and more.
The traits system is defined in this specification as the way a runtime applies and manages operational behaviors to instances of components through traits.
Runtime implementations of the specification MUST provide the traits system for attaching operational behaviors to instances of components.
Traits are applied to component instances in the application configuration.
Traits should be applied into the runtime at installation/upgrade time. Traits should not be checked "lazily", but should be checked when the trait-holding components are created.
When more than one trait is attached to a component, it MUST:
- Apply the traits in the defined order
- Determine compatibility, and fail if the combination of traits cannot be satisfied
Traits are applied in order so that should some set of traits express dependency (e.g. ingress must be set up before SSL), this can be resolved by setting an explicit ordering.
A component instance may only have one configuration of any given trait type.
A deployment SHOULD NOT be marked complete until all trait configurations are completed. For example, if a web server component is deployed with an autoscaler trait, the web server should not be considered "running" until (a) the web server itself is running, as determined by health checks, and (b) the autoscaler trait is running, as determined by the underlying platform.
Components can specify multiple traits, therefore a runtime MUST support the application of zero or more traits to a component. A runtime MUST produce an error and stop deployment if the requested traits cannot be fulfilled by the underlying runtime.
There is no mechanism for explicitly requiring a combination of traits. For example, a trait (ssl
) cannot specify that it requires that another trait (ingress
) be applied to an object. However, implementations of a trait SHOULD fail if one trait cannot fulfill its contract without the presence of another trait. A system MAY support a mechanism in which a trait (sslIngress
) is opaquely backed by two separate trait implementations (ssl
and ingress
).
Any trait that is made available in a runtime MUST be implemented for that runtime. For example, there cannot be a trait for autoscaling
in a runtime with no implementation of that trait.
The traits system is designed to serve as an extension point for runtime operational capability while also providing common, and in some cases, necessary operational functionality for components.
The implementation details of a trait is beyond the scope of this document. However, to allow runtimes to implement arbitrary traits while maintaining a degree commonality for application portability across runtimes, traits are categorized in the following way:
-
Core traits. This specification defines a set of trait definitions with type names in the
core.oam.dev
group. These traits provide operational functionality that is necessary for the operation of certain workload types and component features. Runtimes are REQUIRED to implement these traits as defined in this specification. -
Standard traits. The specification defines a set of trait definitions with type names in the
standard.oam.dev
group. These traits provide operational functionality that is commonly used by application operators and implemented by most runtimes. Runtimes are RECOMMENDED to use the standard trait definitions as defined in this specification when providing equivalent operational functionality to those listed in the standard trait definitions. In other words, if a runtime is implementing trait with behaviorfoo
and a standard trait definition for behaviorfoo
exists, the runtime SHOULD use the standardfoo
trait definition. Application operators that intend to maximize portability of their applications should use these trait definitions when available. Although this does not guarantee portability of an application, it is designed to increase portability across runtimes. -
Extension traits. The specification allows a runtime to define its own set of trait definitions that are unique to that runtime in addition to those defined by core and standard traits. A runtime can choose to implement any set of traits in this category with any definition. Extension trait type names MUST NOT be in the
core.oam.dev
orstandard.oam.dev
groups.
An individual trait MAY be tied to specific workload types (or MAY apply to all workload types). A trait MAY declare which workload types it applies to. For example, an autoscaler trait could apply to a workload type Server, but not to type SingletonServer. The autoscaler trait itself MUST define that restriction if present.
The specification does not set requirements about how simple or complicated a trait may be. Some might expose an expansive set of configurable options while others may expose no configurable options. And it is not required that a trait be as exhaustive as its underlying technology. For example, an implementation of an autoscaling technology may provide numerous ways of determining when a component should scale. But the trait may only surface a few of those. Likewise, multiple autoscaling traits may be defined (each uniquely named), where each trait exposes a different subset of configurable options. Or one large trait may expose all of the possible autoscaling configurations.
Implementations of an OAM runtime SHOULD make all supported traits discoverable in the format explained below.
This section is normative because traits are an inspectable (and possibly shareable) part of the system. All traits MUST be representable in the following format.
Traits are defined with schematics like components. Unlike component schematics, however, each trait definition defines its own unique [properties]((#properties) object schema to define the configuration options for the trait.
The top-level attributes of a trait define its metadata, version, kind, and spec.
Attribute | Type | Required | Default Value | Description |
---|---|---|---|---|
apiVersion |
string |
Y | The specific version of the specification in use. At present only core.oam.dev/v1alpha1 is defined. |
|
kind |
string |
Y | The string Trait |
|
metadata |
Metadata |
Y | Trait metadata. | |
spec |
Spec |
Y | A specification for trait attributes. |
The specification defines two major things: The list of workload types to which this trait applies, and the list of configurable parameters on this trait.
Attribute | Type | Required | Default Value | Description |
---|---|---|---|---|
appliesTo |
[]string |
Y | ["*"] |
The list of workload types that this trait applies to. "*" means any workload type. A trait must apply to at least one workload type. This attribute must contain at least one value. An empty array is invalid for this attribute. |
properties |
Properties |
Y | The trait's configuration options. The value is a JSON schema expressed as json string |
A properties object is an object whose structure is determined by the trait property schema. It may be a simple value, or it may be a complex object.
The value of the properties field in a trait definition is a JSON schema expressed as json string. When a trait is applied to a component instance, the properties are validated against the schema defined in the trait's properties
attribute.
For example, if a trait defines a schema for properties that requires an array of integers with at least one member, the properties object is expected to be an array of integers with at least one member. During validation of the properties object, the trait's property schema will be applied.
This trait definition allows operators to manually scale the number of replicas for workload types that allow scaling operations.
core.oam.dev/v1alpha1.Server
core.oam.dev/v1alpha1.Worker
core.oam.dev/v1alpha1.Task
Attribute | Type | Required | Default Value | Description |
---|---|---|---|---|
replicaCount |
integer |
Y | 0 |
The target number of replicas to create for a given component instance. |
apiVersion: core.oam.dev/v1alpha1
kind: Trait
metadata:
name: ManualScaler
annotations:
version: v1.0.0
description: "Allow operators to manually scale a workloads that allow multiple replicas."
spec:
appliesTo:
- core.oam.dev/v1alpha1.Server
- core.oam.dev/v1alpha1.Worker
- core.oam.dev/v1alpha1.Task
properties: |
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["replicaCount],
"properties": {
"replicaCount": {
"type": "integer",
"description": "the target number of replicas to scale a component to.",
"minimum": 0
}
}
}
The following snippet from an application configuration shows how the manual scaler trait is applied and configured for a component. In this example, it is assumed component frontend
has a workload type of core.oam.dev/v1alpha1.Server
.
apiVersion: core.oam.dev/v1alpha1
kind: ApplicationConfiguration
metadata:
name: custom-single-app
annotations:
version: v1.0.0
description: "Customized version of single-app"
spec:
variables:
components:
- componentName: frontend
instanceName: web-front-end
parameterValues:
traits:
- name: ManualScaler
properties:
replicaCount: 5
This example illustrates using the manual scaler trait to manually scale a component by specifying the number of replicas the runtime should create. This trait has only one attribute in its properties: replicaCount
. This takes an integer, and a value is required for this trait to be successfully applied. If, for example, an application configuration used this trait but did not provide the replicaCount
, the system would reject the application configuration.
Previous Part | Next Part |
---|---|
4. Application Scopes | 6. Application Configuration |