Providers 101: Ordering Pizza with Kubernetes and Crossplane

This is a guest post from Grant Gumina, Principal Product Manager at Upbound who recently built provider-pizza, a Crossplane provider for the Domino’s API. In this post he shares what he learned about providers, and some common mistakes beginners might fall into when writing their first provider.

In my day job, I work at Upbound helping customers deploy and scale Crossplane into production. Our product, Upbound Cloud is a managed service of Crossplane which lets infrastructure operators define and deploy custom cloud APIs and consoles for their teams.

Being “the product guy”, I haven’t written production code in years. However after seeing customers use Crossplane to orchestrate everything from AWS environments to GitHub ticketing systems, I wanted to use my technical background to test the limits of Crossplane's extensibility story. So I spent a weekend figuring out how to order pizza through Crossplane by building a Crossplane provider for the Domino's Pizza API.

If you're not familiar with the project, Crossplane is a cloud-native control plane which allows users to address and abstract infrastructure resources running on-premises or in the cloud(s) of your choice. It does this by installing into a Kubernetes cluster and extending the cluster's API through Providers which install into it.

Each Provider installed into the cluster running Crossplane adds cluster-scoped CRDs for various "managed resources". As a user, you can use kubectl to interact with these resources. For example, Crossplane lets you kubectl apply -f db.yaml to provision a database.

The Project

Provider-pizza is my attempt at learning more about the inner workings of Crossplane, and seeing how far I could extend the metaphor of a "universal cloud API", but it's not the focus of this post. Since Crossplane is still fairly young, there's not a ton of resources geared towards novices. I'm hoping this post can help guide aspiring provider builders in the right direction, and help you avoid some of the mistakes I made when writing my first provider.

View the project on GitHub to learn more about how it works, see how to run it yourself, and order a delicious pizza. Keep reading if you're interested in learning more about Crossplane providers.

https://github.com/grantgumina/provider-pizza/raw/master/demo.gif

The Anatomy of a Provider

Crossplane has an incredible extensibility story, thanks to it's provider model. Providers are basically Kubernetes-style controllers which connect anything with an API to the Kubernetes cluster where Crossplane is running, giving you a CRD representing each resource.

To build my first provider, I cloned the Crossplane team's provider-template repository on GitHub, and got to work.

Managed Resources

Inside of a Provider, you can have many different managed resources, each having it's own Type. In provider-pizza, these Managed Resources are defined in the /apis directory. You can see there's a Managed Resource of type order.

Controllers

Just like a Kubernetes controller, providers run on their own reconciliation loops. The loop has several methods:

Setup - Setup is called as soon as the provider starts up. It registers the controller which triggers these methods to run when events occur.

Connect - Generates a Crossplane ExternalClient which is used to connect to the managed resource. The connect method typically uses the values supplied by the user and defined in ProviderConfig to authenticate with the external service (typically a web API such as the Domino's pizza API). ExternalClient objects will interact with the external resource (in this case the Domino's API) and store connection details as a secret.

Create - I used this method to create the order object based out of the user input and ProviderConfig details. I also set a property on the Order object so I could tell whether or not an order had been requested later on in the Observe method. This let me return ExternalObservation with the appropriate information about whether or not the resource was up to date or created yet.

Observe - This is the mitochondria of the controller. The Observe method gets called every few seconds and updates the object's status. This is where I made calls to Domino's tracker API to get the latest information about my order. Returning an ExternalObservation object with ResourceExists set to false calls the Create method. This is the default behavior of provider-template.

I didn't initially realize this, and the poor workers at the Wallingford Dominos fulfilled half a dozen or so orders before I caught the mistake.

Update - This method gets called when the ResourceUpToDate property on the ExternalObservation object is set to false. I didn't use this method for anything, but if the Domnio's API supported order modification, I suspect this is where I would handle that.

Delete - This method gets called when the ResourceExists property on the ExternalObservation object is set to false.

ProviderConfig

Crossplane Providers can be configured with secrets for authentication or other user defined values by applying a ProviderConfig, a CRD type which gets installed by the user. ProviderConfigs have a standard format Crossplane expects to read when configuring a Provider. They can reference Kubernetes secrets, so users can store credentials needed to connect to the web services.

In provider-pizza, payment information is stored as a secret and referenced by the ProviderConfig:

apiVersion: v1
kind: Secret
metadata:
  name: payment-secret
  namespace: crossplane-system
type: Opaque
data:
  credentials.json: ewogIGNyZWRlbnRpYWxzOiBCQVNFNjRFTkNPREVEX1BST1ZJREVSX0NSRURTLAogIFR5cGU6ICJDcmVkaXRDYXJkIiwKICBDYXJkVHlwZTogIlZpc2EiLAogIE51bWJlcjogIjExMTEyMjIyMzMzMzQ0NDQiLAogIEV4cGlyYXRpb246ICIwMTIzIiwKICBTZWN1cml0eUNvZGU6ICIxMjMiLAp9
---
apiVersion: provider-pizza.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
  name: example
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: payment-secret
      key: payment-secret-key

Wrapping Up

Crossplane is designed with extensibility in mind. Typically users orchestrate cloud and on-premises  infrastructure with the project, but as you can see, any service with an API can be used as well. Once installed, providers give Crossplane users a uniform interface and API to orchestrate and manipulate the managed resources they represent.

We saw how you can kubectl -f apply order.yaml, but you can just as easily kubectl -f apply database.yaml using another provider like provider-aws.

If you’re interested in learning more about how you can use Crossplane to address and abstract your own infrastructure, even if it’s not pizza related, we’d love to talk with you. Join the community Slack and follow us on Twitter.

Looking to deploy Crossplane into production, or already doing so? We’d love to give you a demo of Upbound Cloud and see how it might help with your use case. Visit us at upbound.io or email info@upbound.io to learn more.


Get involved!

We're excited to see the continual growth of the Crossplane community and would love for you to get involved. Whether you are a developer, user, or just interested in what we're up to, feel free to join us via one of the following methods:

Keep up with Upbound

* indicates required