Build, Publish, and Install Crossplane Package

MorningSpace
6 min readAug 9, 2021

Crossplane is a CNCF sandbox project which can extend the Kubernetes API to manage and compose infrastructure. It allows you to assemble infrastructure from multiple vendors and expose as higher level abstraction to consume without having to write any code.

As the second post of my Crossplane journey, in this post, I will share with you what I learned with some opinionated tips as complements to the official Crossplane documents on how to build, publish and install Crossplane package.

About Crossplane Package

Before jump into the details, below is a very quick recap of what Crossplane package is. You can find more detailed information about Crossplane package from Crossplane documentation.

Crossplane packages are opinionated OCI images that contain a stream of YAML that can be parsed by the Crossplane package manager. It can be pushed to and pulled from any OCI-compatible registry such as Docker Hub.

There are two types of packages: provider package and configuration package. Provider extends Crossplane to enable infrastructure resource provisioning. As an example, in my case, I am using provider-kubernetes, which can enable deployment and management of arbitrary Kubernetes resources on clusters. Provider package is used to wrap up and distribute provider so that can be installed and consumed by other people.

On the other hand, configuration package is used to wrap up a set of CompositeResourceDefinition and Composition definitions in YAML, which can be installed on clusters so that you can leverage the powerful composition capability provided by Crossplane to quickly compose different resources together to provision infrastructure that meets your specific need.

The crossplane.yaml file

No matter it is for provider or configuration, all packages must have a file called crossplane.yaml in the root directory. For providers, it is typically located at the package folder in the project root directory. This file contains the package’s metadata which governs how Crossplane will install the package.

In my case, I have both provider and configuration where the configuration depends on the provider which requires it to be installed at first. The provider dependency along with its version constraint is defined in crossplane.yaml. If the dependency is not installed, Crossplane will install it at the latest version that fits within the provided constraints. As an example, below is the crossplane.yaml file for my configuration package:

apiVersion: meta.pkg.crossplane.io/v1
kind: Configuration
metadata:
name: my-test-configuration
annotations:
provider: kubernetes
spec:
crossplane:
version: ">=v1.0.0-0"
dependsOn:
- provider: quay.io/morningspace/provider-kubernetes-amd64
version: ">=v0.0.0-0"

There are couple of things to note:

  • When specify the provider as dependency using spec.dependsOn, you should not append the provider version (i.e. the image tag) after the provider name. This may lead to the missing dependency error when Crossplane tries to install the provider.
  • When specify the version constraint, you should strictly follow the semver spec. Otherwise, it may not be able to find the appropriate version even dependency is found. This may lead to the incompatible dependency error.

In the above example, the configuration package depends on a provider where the provider image is pulled from quay.io/morningspace/provider-kubernetes-amd64. It also defines the version constraint ">=v0.0.0-0which means all versions started from v0.0.0 including all prerelease versions are qualified to be installed to serve this configuration package.

Build Package

According to Crossplane documentation, it is recommended to use Crossplane CLI to build Crossplane package, which essentially works as a kubectl plugin. To build a package, you can navigate to the package root directory and choose one of the below commands to execute, depending on which type of package you are going to build:

# To build a provider package
kubectl crossplane build provider
# To build a configuration package
kubectl crossplane build configuration

Besides that, if you build package for a provider, you can also use make. To build source code and other artifacts including the package for host platform, just run below command in project root directory:

make build

To build for all platforms, you can run:

make build.all

Publish Package

After you successfully build the package locally, you can push the package to the target registry. If you use Crossplane CLI, again, navigate to the package root directory and choose one of the below commands to execute, depending on which type of package you are going to push:

# To push a provider package
kubectl crossplane push provider $ORG_NAME/$PROVIDER_NAME:$PROVIDER_VERSION
# To push a configuration package
kubectl crossplane push configuration $ORG_NAME/$CONFIGURATION_NAME:$CONFIGURATION_VERSION

As an example, I want to push my configuration package with version v0.0.1 to quay.io:

kubectl crossplane push configuration quay.io/morningspace/my-test-configuration:v0.0.1

Besides that, if you publish package for a provider, you can also use make. To push the package to the registry, just run below command in project root directory:

make publish

If you only want to publish package for a specific platform, e.g. linux_amd64, to the registry, you can specify PLATFORMS before run make publish. For example:

PLATFORMS=linux_amd64 make publish

Push to Registry other than Docker Hub

By default, most Crossplane providers use Docker Hub as the target registry. In order to push package to other registry such as quay.io. You got two things to do.

Firstly, you need to modify the provider crossplane.yaml file, change the spec.controller.image to point to the right registry. Otherwise, when install the provider, it will still go back to check Docker Hub. Below is an example:

apiVersion: meta.pkg.crossplane.io/v1alpha1
kind: Provider
metadata:
name: provider-kubernetes
annotations:
descriptionShort: |
The Crossplane Kubernetes provider enables management of Kubernetes Objects.
spec:
controller:
image: quay.io/morningspace/provider-kubernetes-controller-amd64:VERSION

Secondly, most providers define DOCKER_REGISTRY with the default value crossplane in their Makefile files. This is required by the provider build scripts to push to the target registry. This also needs to be updated to reflect the new registry. For example:

# DOCKER_REGISTRY = crossplane
DOCKER_REGISTRY = quay.io/morningspace

Install Package

Now that you have pushed provider and configuration packages to the target registry, you can install them on clusters using Crossplane CLI from anywhere. Choose one of the below commands to execute, depending on which type of package you are going to install:

# To install a provider package
kubectl crossplane install provider $ORG_NAME/$PROVIDER_NAME:$PROVIDER_VERSION
# To install a configuration package
kubectl crossplane install configuration $ORG_NAME/$CONFIGURATION_NAME:$CONFIGURATION_VERSION

As an example, I want to install my configuration package with version v0.0.1 from quay.io:

kubectl crossplane install configuration quay.io/morningspace/my-test-configuration:v0.0.1

If it is installed successfully, you should see the CompositeResourceDefinition and Composition definitions included in this package are installed on the cluster. It will also install its dependency, the provider package.

Troubleshooting Tips

To verify the install results, you can run:

kubectl get crossplane

It will list all the CompositeResourceDefinition and Composition resources, the Provider and ProviderRevision resources, the Configuration and ConfigurationRevision resources that are currently available on the cluster.

Pay attention to the column INSTALLED and HEALTHY for Provider and Configuration resources, as well as the column HEALTHY for ProviderRevision and ConfigurationRevision resources, they all need to be TRUE. Otherwise, there must be some errors occurred during the installation. For example:

NAME                                                                INSTALLED   HEALTHY   PACKAGE                                                             AGE
provider.pkg.crossplane.io/morningspace-provider-kubernetes-amd64 True True quay.io/morningspace/provider-kubernetes-amd64:v0.0.0-29.ga50151c 6d
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
providerrevision.pkg.crossplane.io/morningspace-provider-kubernetes-amd64-f098e786458d True 1 quay.io/morningspace/provider-kubernetes-amd64:v0.0.0-29.ga50151c Active 6d
NAME INSTALLED HEALTHY PACKAGE AGE
configuration.pkg.crossplane.io/morningspace-my-test-configuration True True quay.io/morningspace/my-test-configuration:v0.0.1 5d1h
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
configurationrevision.pkg.crossplane.io/morningspace-my-test-configuration-6c6e494f5a22 True 1 quay.io/morningspace/my-test-configuration:v0.0.1 Active 1 1 5d1h

Also, in the above example, the ConfigurationRevision shows that there is one dependency found (DEP-FOUNDis 1) and it has been installed successfully (DEP-INSTALLED is 1).

When there are errors, you can run below command which gives you detailed information of all packages that are being installed:

kubectl get lock -o yaml

To inspect a specific provider or configuration package that is in issue, you can run kubectl describe against the corresponding resource, e.g. the Provider and ProviderRevision resources for provider, the Configuration and ConfigurationRevision resources for configuration. Usually, you should be able to know the error reason by checking the Status and Events field.

--

--

MorningSpace

Life is coding and writing! I am a software engineer who have been in IT field for 10+ years. I would love to write beautiful code and story for people.