In this specification software products are comprised of logical units called components. A component version consists of a set of technical artifacts (e.g., Docker images, Helm charts, binaries, configuration data, etc.). Such artifacts are called resources in this specification. Resources are usually built from something, e.g., code in a git repo. Those are named sources in this specification.
OCM introduces a Component Descriptor for every component version that
describes the resources, sources, and other component versions belonging to a particular
component version and how to access them.
Usually, however, real-life applications are composed of multiple components. For
example, an application might consist of a frontend, a backend, a database, and a web server.
During the software development process new
component versions
are created and third-party components might be consumed from a public registry and
updated from time to time.
Not all component version combinations of frontend, backend, database, etc. are
compatible and form a valid product version. In order to define reasonable
version combinations for the software product, we could use another feature of
the Component Descriptor, called a Component Reference (or reference in short), which allows
the aggregation of component versions.
For each component and each version in use, there is a Component Descriptor. For the
entire application, we introduce a new component that describes the overall software
product referencing all components. This describes the entire application.
A particular version of this application is again described by a Component Descriptor,
which contains references to the Component Descriptors of its components in their version in
use. You are not restricted to this approach. It is, e.g., possible to create multi-level
hierarchies or you could just maintain a list of component version combinations which build
a valid product release.
In a nutshell, OCM provides a simple approach to specify what belongs to a product version.
Starting with the Component Descriptor for a product version and following the component
references, you could collect all artifacts belonging to this product version.
We assume that you have already read the guides in the Getting Started section, as this guide discusses a more
complex scenario using plain Localizations and Configurations without the use of Unpacker.
The following component file describes four components: three components that represent the podinfo microservices and one aggregate component that brings together the podinfo components using references. We refer to the aggregate component as the product component.
With the components modeled we can start to build a component archive using the ocm cli:
This will create a folder called component-archive. The structure of that should look something like this:
These blobs contain the resources we described when modelling our podinfo application. If we cat a random blob we get
something like this:
Next, we transfer this component to a location of your choice. Here <your-location> for me was ghcr.io/skarlso/demo-component.
With the transfer completed, we now have a component version that we can use and deploy throughout this example.
The backend files contain the following relevant data:
manifests
configmap.yaml
contains configuration options such as PODINFO_UI_COLOR
deploy.yaml
the deployment configuration. Note that this deployment yaml contains an attribute image that will be configured using the config.yaml explained below.
kustomization.yaml makes sure only the relevant files are applied
service.yaml to expose the service endpoint and make discoverable
config.yaml
contains the configuration and localization rules which will be applied to the deployment file.
Localization
will use an image resource to replace the above value for the atribute image with the correct one
Configuration
will use the config information to configure some default values for those values such as color and message.
We start by creating an image pull secret since the component that we just transferred was placed in a private OCI registry. The pull secret will be
used by the OCM client or OCM controller to access this package in ghcr. To create the secret, run:
Now we create a ComponentVersion custom resource that will trigger a reconcile of the podinfo component.
This will reconcile the ComponentDescriptor for the specific version, making the component metadata available for
other Kubernetes resources to consume. If everything was successful, we can inspect the created component version:
The important bits here are the references. These are all the components that the top component contains. These references are used to fetch and identify component dependencies. This component will also contain which version was last reconciled.
We can also examine the component descriptors using the following command:
This descriptor specifies the location of the component’s resource based on the current context (globalAccess). We can use this information to retrieve the resource from a storage layer that is accessible within our current environment.
Both, localization and configuration, are in the ConfigData object. So we point to that. The controller will use the
image resource to localize the backend image. This is how it’s defined in the localizations rule:
Now, let’s construct these objects:
Finally, let’s add the FluxDeployer too, which makes sure that this component is deployed to the target location.
Redis is exactly the same as the above two. Just with different names and pointing to the redis resource. Try creating
these yourself to see if you understood the structure. If you get stuck, you can always take a peek under
podinfo/redis/components.
To apply them, simply run this command from the podinfo root:
The ocm-controller creates ComponentDescriptor resources for each referenced component version. Those component descriptors
contain all the resources that those versions have, such as manifest files, configuration files, deployment files, etc.
It will use this dependency graph to lookup resource data in the right component version.
Let’s take a look at each object in the cluster next.
All of the components should have their Localization, Configuration, and FluxDeployer.
Let’s take a look at the configuration object next (very similar to localization):
The important details here are the configRef field and the sourceRef field. The configRef field defines where the
configuration values are located at:
Note. This configuration has a source that is pointing at the Localization resource that we created. This is
important because the configuration needs to work on the localized entities. Once reconciled, it will create a
Snapshot. That snapshot contains the input resources that have been transformed using the supplied configuration rules.
Next, comes the FluxDeployer. The FluxDeployer will point to the last Snapshot in the chain of transformations
which is the Configuration. It looks something like this:
This creates a Kustomization object. The Kustomization
object is used to reconcile the created component into the target namespace. We have three of these for each component
for which we would like to apply the results.
Once all objects are applied, we should see podinfo deployed in the default namespace:
Note: The pod count might vary based on the default settings in the configuration data.
If the deployment isn’t appearing, there are several places to check for errors:
Flux:
Maybe Flux didn’t kick in yet. Try to force a reconcile by running:
Events:
Kubernetes Events could hold some extra information. List the most recent ones with:
Logs:
Sometimes, you can see errors in the source-controller failing to get the right resources. Or kustomize-controller
doesn’t understand something. We’ll go into getting logs in Controller Logs section.
Object Status:
Many of the objects have a status with the most recent error on them. The relevant objects in this case are the
FluxDeployer and the OCIRepository objects. Make sure they have successful statuses.
Flux has a couple of controllers we can check if things don’t start up (especially if we don’t see any resources in the
cluster, or if we don’t see the podinfo deployment being started).
source-controller:
This controller will contain information about the latest applied code from the repository. If there is an error here
it means that the source, or rather our modifications, weren’t applied.
kustomize-controller:
This controller will contain information about reconciled objects. A Kustomization source is usually either a
GitRepository or an OCIRepository. In this case, the source will be an OCIRepositoy. That repository is pointing to
the in-cluster OCI repository. A snapshot creates these entries and that’s where it loads the data from.
The helm-controller and notification-controller aren’t relevant.
The ComponentVersion object contains information about what components have been reconciled. We talked about that
earlier at Component Version. The Status section contains any errors that could have
occurred when reconciling information.
ComponentDescriptor:
The ComponentDescriptor holds information about each component and their resources. Read more at ComponentDescriptors.
If the resources section is empty in the status, there is something wrong reconciling the individual items.
Localization:
The status section contains information about the snapshot that this object created. The snapshot is used to point
to the right repository in the internal OCI cache. It also contains the last applied version. The conditions
section will contain any errors while reconciling the resource.
Configuration:
The status section contains information about the snapshot that this object created. The snapshot is used to point
to the right repository in the internal OCI cache. It also contains the last applied version. The conditions
section will contain any errors while reconciling the resource.
Snapshots:
The Snapshot, most of the time, is transparent to the user. The sources are Snapshot providers. That means any object
that can produce a Snapshot can be a source to a Localization, Configuration or a Resource object. A Source is a thing
from which to fetch resource data such as Manifests, rules, Markdown files, descriptors, etc.
We can also use Snapshots to look for errors in reconciling resource data. A Snapshot’s status contains information.
This Snapshot contains a lot of information about what has been replicated in the internal registry. We can use crane
to fetch it and check the generated content.
FluxDeployer:
FluxDeployer is used to apply the generated objects to a cluster. In the background, it’s leveraging Flux’s
Kustomization object. This object’s status will contain any errors that could occur during applying generated content,
like invalid data, invalid CRDs, invalid yaml, no access to the cluster, permission issues, etc. Each component has a
FluxDeployer applying some kind of component data to the cluster such as, Deployments, ConfigMaps,
ReplicaSets, etc.
OCIRepository:
There should be one OCIRepository resource per component. The OCIRepository is created by the FluxDeployer. OCIRepository will
contain any errors regarding the content of the internal registry.
Kustomization:
Kustomization objects are also created by the FluxDeployer. These objects will contain applying errors.
Usually, this means that the content we are trying to sync from the OCIRepository is not a tar file. This can happen if
the resource wasn’t a Directory or if the fetching of the data somehow failed.
To run crane, first, expose the internal registry using port-forward like this:
Then, verify that the connection is working by running a catalog command:
This should list something like this:
To identify which of these contains our failed resource, check the failing OCIRepository object.
Now we know which of these contains the invalid resource. We can further identify which blob it is by either, describing the
relevant snapshot, or by running a manifest command with crane.
One of these will not be what they seem. To fetch a blob run:
And then check what that temp.tar looks like. If the content is human-readable, there is a problem. If you encounter
the component descriptor file, you can skip that. That’s not what you are looking for.