Configuring Crossplane with Argo CD

This document is for an older version of Crossplane.

This document applies to Crossplane v2.3 and not to the latest release v2.4.

Argo CD and Crossplane are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes cluster into a Universal Control Plane for all your resources. Configuration details are required in order for the two to work together properly. This doc will help you understand these requirements. It is recommended to use Argo CD version 2.4.8 or later with Crossplane.

Argo CD synchronizes Kubernetes resource manifests stored in a Git repository with those running in a Kubernetes cluster (GitOps). Argo CD has different ways to configure how it tracks resources. With Crossplane, you need to configure Argo CD to use Annotation based resource tracking. See the Argo CD docs for additional detail.

Configuring Argo CD with Crossplane

Set resource tracking method

In order for Argo CD to track Application resources that contain Crossplane related objects, configure it to use the annotation mechanism.

To configure it, edit the argocd-cm ConfigMap in the argocd Namespace as such:

1apiVersion: v1
2kind: ConfigMap
3data:
4  application.resourceTrackingMethod: annotation

Set health status

Argo CD has a built-in health assessment for Kubernetes resources. The community directly supports some checks in Argo’s repository. For example the Provider from pkg.crossplane.io already exists which means there no further configuration needed.

Argo CD also enable customising these checks per instance, and that’s the mechanism used to provide support of Provider’s CRDs.

Argo CD v3.1 and later also include built-in health checks for the *.crossplane.io and *.upbound.io API groups. An entry in resource.customizations, including a wildcard entry like the ones in the following example, takes precedence over the built-in health checks for the resources it matches.

To configure it, edit the argocd-cm ConfigMap in the argocd Namespace.

Note
ProviderConfig may have no status or a status.users field.

  1apiVersion: v1
  2kind: ConfigMap
  3data:
  4  application.resourceTrackingMethod: annotation
  5  resource.customizations: |
  6    "*.upbound.io/*":
  7      health.lua: |
  8        health_status = {
  9          status = "Progressing",
 10          message = "Provisioning ..."
 11        }
 12
 13        local function contains (table, val)
 14          for i, v in ipairs(table) do
 15            if v == val then
 16              return true
 17            end
 18          end
 19          return false
 20        end
 21
 22        local has_no_status = {
 23          "ClusterProviderConfig",
 24          "ProviderConfig",
 25          "ProviderConfigUsage"
 26        }
 27
 28        if obj.status == nil or obj.status.conditions == nil or next(obj.status.conditions) == nil then
 29          if contains(has_no_status, obj.kind) then
 30            health_status.status = "Healthy"
 31            health_status.message = "Resource is up-to-date."
 32            if obj.status ~= nil and obj.status.users ~= nil then
 33              health_status.message = "Resource is in use."
 34            end
 35          end
 36          return health_status
 37        end
 38
 39        for i, condition in ipairs(obj.status.conditions) do
 40          if condition.type == "LastAsyncOperation" then
 41            if condition.status == "False" then
 42              health_status.status = "Degraded"
 43              health_status.message = condition.message
 44              return health_status
 45            end
 46          end
 47
 48          if condition.type == "Synced" then
 49            if condition.status == "False" then
 50              health_status.status = "Degraded"
 51              health_status.message = condition.message
 52              return health_status
 53            end
 54          end
 55
 56          if condition.type == "Ready" then
 57            if condition.status == "True" then
 58              health_status.status = "Healthy"
 59              health_status.message = "Resource is up-to-date."
 60            end
 61          end
 62        end
 63
 64        return health_status
 65
 66    "*.crossplane.io/*":
 67      health.lua: |
 68        health_status = {
 69          status = "Progressing",
 70          message = "Provisioning ..."
 71        }
 72
 73        local function contains (table, val)
 74          for i, v in ipairs(table) do
 75            if v == val then
 76              return true
 77            end
 78          end
 79          return false
 80        end
 81
 82        local has_no_status = {
 83          "Composition",
 84          "CompositionRevision",
 85          "DeploymentRuntimeConfig",
 86          "EnvironmentConfig",
 87          "ImageConfig",
 88          "ClusterProviderConfig",
 89          "ProviderConfig",
 90          "ProviderConfigUsage"
 91        }
 92        if obj.status == nil or obj.status.conditions == nil or next(obj.status.conditions) == nil then
 93          if contains(has_no_status, obj.kind) then
 94            health_status.status = "Healthy"
 95            health_status.message = "Resource is up-to-date."
 96            if obj.status ~= nil and obj.status.users ~= nil then
 97              health_status.message = "Resource is in use."
 98            end
 99          end
100          return health_status
101        end
102
103        for i, condition in ipairs(obj.status.conditions) do
104          if condition.type == "LastAsyncOperation" then
105            if condition.status == "False" then
106              health_status.status = "Degraded"
107              health_status.message = condition.message
108              return health_status
109            end
110          end
111
112          if condition.type == "Synced" then
113            if condition.status == "False" then
114              health_status.status = "Degraded"
115              health_status.message = condition.message
116              return health_status
117            end
118          end
119
120          if condition.type == "Installed" then
121            if condition.status == "False" then
122              health_status.status = "Degraded"
123              health_status.message = condition.message
124              return health_status
125            end
126          end
127
128          if contains({"Ready", "Healthy", "Offered", "Established", "ValidPipeline", "RevisionHealthy"}, condition.type) then
129            if condition.status == "True" or (condition.type == "Established" and condition.reason == "InactiveManagedResource") then
130              health_status.status = "Healthy"
131              health_status.message = "Resource is up-to-date."
132            end
133          end
134        end
135
136        return health_status

Set resource exclusion

Crossplane providers generate a ProviderConfigUsage for each managed resource (MR) they handle. This resource enables representing the relationship between MR and a ProviderConfig so that the controller can use it as a finalizer when you delete a ProviderConfig. End users of Crossplane don’t need to interact with this resource.

A growing number of resources and types can impact Argo CD UI reactivity. To help keep this number low, Crossplane recommend hiding all ProviderConfigUsage resources from Argo CD UI.

To configure resource exclusion edit the argocd-cm ConfigMap in the argocd Namespace as such:

1apiVersion: v1
2kind: ConfigMap
3data:
4    resource.exclusions: |
5      - apiGroups:
6        - "*"
7        kinds:
8        - ProviderConfigUsage

The use of "*" as apiGroups enables the mechanism for all Crossplane Providers.

Increase Kubernetes client QPS

As the number of CRDs grow on a control plane it increases the amount of queries Argo CD Application Controller needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.

Set the environment variable ARGOCD_K8S_CLIENT_QPS to 300 for improved compatibility with multiple CRDs.

The default value of ARGOCD_K8S_CLIENT_QPS is 50, modifying the value also updates ARGOCD_K8S_CLIENT_BURST as it is default to ARGOCD_K8S_CLIENT_QPS x 2.