Configuring Crossplane with Argo CD

This document is for a preview version of Crossplane.

This document applies to Crossplane v2.0-preview and not to the latest release v1.19.

Don't use Crossplane v2.0-preview in production.

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 of 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 correctly track Application resources that contain Crossplane related objects it needs to be configured 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. Some checks are supported by the community directly in Argo’s repository. For example the Provider from pkg.crossplane.io has already been declared 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.

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          "ProviderConfig",
 24          "ProviderConfigUsage"
 25        }
 26
 27        if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
 28          health_status.status = "Healthy"
 29          health_status.message = "Resource is up-to-date."
 30          return health_status
 31        end
 32
 33        if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
 34          if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
 35            health_status.status = "Healthy"
 36            health_status.message = "Resource is in use."
 37            return health_status
 38          end
 39          return health_status
 40        end
 41
 42        for i, condition in ipairs(obj.status.conditions) do
 43          if condition.type == "LastAsyncOperation" then
 44            if condition.status == "False" then
 45              health_status.status = "Degraded"
 46              health_status.message = condition.message
 47              return health_status
 48            end
 49          end
 50
 51          if condition.type == "Synced" then
 52            if condition.status == "False" then
 53              health_status.status = "Degraded"
 54              health_status.message = condition.message
 55              return health_status
 56            end
 57          end
 58
 59          if condition.type == "Ready" then
 60            if condition.status == "True" then
 61              health_status.status = "Healthy"
 62              health_status.message = "Resource is up-to-date."
 63              return health_status
 64            end
 65          end
 66        end
 67
 68        return health_status
 69
 70    "*.crossplane.io/*":
 71      health.lua: |
 72        health_status = {
 73          status = "Progressing",
 74          message = "Provisioning ..."
 75        }
 76
 77        local function contains (table, val)
 78          for i, v in ipairs(table) do
 79            if v == val then
 80              return true
 81            end
 82          end
 83          return false
 84        end
 85
 86        local has_no_status = {
 87          "Composition",
 88          "CompositionRevision",
 89          "DeploymentRuntimeConfig",
 90          "ProviderConfig",
 91          "ProviderConfigUsage"
 92        }
 93        if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
 94            health_status.status = "Healthy"
 95            health_status.message = "Resource is up-to-date."
 96          return health_status
 97        end
 98
 99        if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
100          if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
101            health_status.status = "Healthy"
102            health_status.message = "Resource is in use."
103            return health_status
104          end
105          return health_status
106        end
107
108        for i, condition in ipairs(obj.status.conditions) do
109          if condition.type == "LastAsyncOperation" then
110            if condition.status == "False" then
111              health_status.status = "Degraded"
112              health_status.message = condition.message
113              return health_status
114            end
115          end
116
117          if condition.type == "Synced" then
118            if condition.status == "False" then
119              health_status.status = "Degraded"
120              health_status.message = condition.message
121              return health_status
122            end
123          end
124
125          if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
126            if condition.status == "True" then
127              health_status.status = "Healthy"
128              health_status.message = "Resource is up-to-date."
129              return health_status
130            end
131          end
132        end
133
134        return health_status    

Set Resource Exclusion

Crossplane providers generates a ProviderConfigUsage for each of the managed resource (MR) it handles. This resource enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a ProviderConfig is deleted. End users of Crossplane aren’t expected to interact with this resource.

Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we 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 will enable the mechanism for all Crossplane Providers.

Increase Kubernetes Client QPS

As the number of CRDs grow on a control plane it will increase 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 a large number of CRDs.

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