Crossplane: How do providers work
Tags: devops, kubernetes, iac, crossplane
Published: 2023-10-17 07:16:35

Providers are the meat around Crossplane’s bones, and they are used to extend the capabilities of Crossplane. When Crossplane is installed, it doesn't have any capabilities to interact with external systems. A core Crossplane pod will only watch the following resources.

```
compositeresourcedefinitions.apiextensions.crossplane.io
compositionrevisions.apiextensions.crossplane.io    	 
compositions.apiextensions.crossplane.io            	 
configurationrevisions.pkg.crossplane.io            	 
configurations.pkg.crossplane.io                    	 
controllerconfigs.pkg.crossplane.io                 	 
locks.pkg.crossplane.io                             	 
providerrevisions.pkg.crossplane.io                 	 
providers.pkg.crossplane.io                         	 
storeconfigs.secrets.crossplane.io
```

When you install a provider, a new pod is created to Crossplane's installation namespace. This pod is a Kubernetes Controller that watches the CRDs that are also installed as part of the provider package.

To find out what different kinds of providers are available, you can check the [Upbound Marketplace](https://marketplace.upbound.io/) and [crossplane-contrib](https://github.com/crossplane-contrib) repository. For this series, we are going to work with the following providers:

- [provider-gcp-storage](https://marketplace.upbound.io/providers/upbound/provider-gcp-storage/v0.37.0)
- [provider-gcp-cloudplatform](https://marketplace.upbound.io/providers/upbound/provider-gcp-cloudplatform/v0.37.0)
- [provider-terraform](https://marketplace.upbound.io/providers/upbound/provider-terraform/v0.10.0)

Those GCP providers are installed from the [provider-family-gcp](https://marketplace.upbound.io/providers/upbound/provider-family-gcp/v0.37.0/docs) package. These provider-family packages are special packages that allow you to install only the provider packages you need instead of installing everything, which would mean 343 CRDs if you install the [provider-gcp](https://marketplace.upbound.io/providers/upbound/provider-gcp/v0.37.0/docs) package instead. Crossplane also states:

> On average, 30 CRDs are used from Provider packages.

Looking at the average number, you would still have ~313 CRDs in the cluster that aren't used :exploding_head:.

Install the providers

```yaml
cat <<EOF | kubectl apply --filename=-
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-gcp-storage
spec:
  package: xpkg.upbound.io/upbound/provider-gcp-storage:v0.36.0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-gcp-cloudplatform
spec:
  package: xpkg.upbound.io/upbound/provider-gcp-cloudplatform:v0.36.0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-terraform
spec:
  package: xpkg.upbound.io/upbound/provider-terraform:v0.10.0
EOF
```

After a little while, you should see the providers installed and in a healthy state

```
kubectl get provider
---
NAME                          INSTALLED   HEALTHY   PACKAGE                                                      AGE
provider-gcp-cloudplatform    True        True      xpkg.upbound.io/upbound/provider-gcp-cloudplatform:v0.36.0   116s
provider-gcp-storage          True        True      xpkg.upbound.io/upbound/provider-gcp-storage:v0.36.0         116s
provider-terraform            True        True      xpkg.upbound.io/upbound/provider-terraform:v0.10.0           116s
upbound-provider-family-gcp   True        True      xpkg.upbound.io/upbound/provider-family-gcp:v0.37.0          107s
```

Now the providers are installed and ready, we need to set up the ProviderConfig, which configures the credentials for the provider to be able to interact with external systems, in this case, with Google Cloud. You can have multiple ProviderConfigs and reference them in managed resources using `providerConfigRef`. ProviderConfigs are cluster-scoped resources.

You can set up a ProviderConfig per tenant when you have a multi-tenant cluster. When creating compositions, you could patch the value of `providerConfigRef` in managed resources with a value of `spec.claimRef.namespace`, which points to the namespace where the XRC was created.

![Multi-tenant providerconfig](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/di6k0b4x3rps2ju1hxub.png)


Every provider has their own individual settings available when it comes to ProviderConfig. For the GCP provider, you can find all the available configuration options [here](https://marketplace.upbound.io/providers/upbound/provider-family-gcp/v0.37.0/resources/gcp.upbound.io/ProviderConfig/v1beta1) and for Terraform provider [here](https://marketplace.upbound.io/providers/upbound/provider-terraform/v0.10.0/resources/tf.upbound.io/ProviderConfig/v1beta1). If you need to override Controller related settings eg. ServiceAccount you can use [ControllerConfig](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1) for that.

In upcoming chapters, we will create resources in Google Cloud that involve creating a bucket, serviceaccount, iam-binding, and serviceaccountkey. Use the following to configure a new service account with needed permissions in GCP:

```
# GCP Project ID
PROJECT_ID=""

gcloud iam service-accounts create crossplane-sa-demo --display-name "Crossplane Service Account Demo"

gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:crossplane-sa-demo@$PROJECT_ID.iam.gserviceaccount.com --role roles/storage.admin
gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:crossplane-sa-demo@$PROJECT_ID.iam.gserviceaccount.com --role roles/iam.serviceAccountAdmin
gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:crossplane-sa-demo@$PROJECT_ID.iam.gserviceaccount.com --role roles/iam.serviceAccountKeyAdmin
gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:crossplane-sa-demo@$PROJECT_ID.iam.gserviceaccount.com --role roles/storage.iamMember
```

Create a service account key:

```
gcloud iam service-accounts keys create credentials.json --iam-account=crossplane-sa-demo@$PROJECT_ID.iam.gserviceaccount.com
```

Create a Kubernetes secret in `crossplane-system` namespace that contains the previously created credentials:

```
kubectl create secret generic gcp-creds --from-file=creds=./credentials.json -n crossplane-system
```

Create ProviderConfig that uses these credentials:

```yaml
cat <<EOF | kubectl apply --filename=-
apiVersion: gcp.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  projectID: $PROJECT_ID
  credentials:
	source: Secret
	secretRef:
  	name: gcp-creds
  	namespace: crossplane-system
  	key: creds
EOF
```

If you run this inside GKE, using the Workload Identity for authentication is much better. You can find detailed instructions for it [here](https://marketplace.upbound.io/providers/upbound/provider-family-gcp/v0.37.0/docs/configuration).

You can also read the secret from the filesystem using `fs`. This might come in handy in cases where you are leveraging, for example, Hashicorp Vault with Vault Agent sidecar to inject secrets to pods. Here is a quick example of how you would configure it without going into too much detail about how to work with Vault Agent Injector:

```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
  name: gcp-config
spec:
  metadata:
	annotations:
  	vault.hashicorp.com/agent-inject: "true"
  	vault.hashicorp.com/role: "crossplane-providers"
  	...
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-gcp-storage
spec:
  package: xpkg.upbound.io/upbound/provider-gcp-storage:v0.36.0
  controllerConfigRef:
	name: gcp-config
---
apiVersion: gcp.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  projectID: $PROJECT_ID
  credentials:
	source: Filesystem
	fs:
  	  path: /vault/secrets/gcp-creds
```

Now we can quickly test that everything is working by creating a [Bucket](https://marketplace.upbound.io/providers/upbound/provider-gcp-storage/v0.37.0/resources/storage.gcp.upbound.io/Bucket/v1beta1) resource:

```yaml
cat <<EOF | kubectl apply --filename=-
apiVersion: storage.gcp.upbound.io/v1beta1
kind: Bucket
metadata:
  name: ps-bucket-${RANDOM}
spec:
  forProvider:
	location: US
EOF
```

After a little while, you should see the bucket resource ready and synced:

```
kubectl get bucket
---
NAME                       	    READY   SYNCED   EXTERNAL-NAME              	AGE
bucket-crossplane-demo-30855    True	True 	 bucket-crossplane-demo-30855   15m
```

At this point, we are ready to start working with GCP using Crossplane. I will go through setting up the Terraform provider configs later in the series when it's time to start working with it.

Remember to delete the test bucket resource:

```
kubectl delete bucket <bucket_name>
```

The next chapter quickly reviews available configuration options for managed resources.