crossplane-contrib/provider-kubernetes

Crossplane Kubernetes Provider does not create the status fields in target k8s cluster

ashwinshirva opened this issue · 8 comments

What happened?

I am using crossplane claim, XR, composition and kubernetes provider to create a kubernetes custon resource in an target kubernetes cluster. The custom resource is getting created in the target cluster, but only with the spec fields. I have included the status field as well in the managed resource defined in crossplane composition, but looks the kubernetes provider does not create the status part, it creates only the spec part of the custom resource in external cluster

How can we reproduce it?

Claim YAML

apiVersion: example.org/v1alpha1
kind: MyCr
metadata:
  namespace: default
  name: my-cr-1
spec:
  parameters:
    field2: "sample field2"
  compositionRef:
    name: mycrcomposition
  writeConnectionSecretToRef:
    name: mymy-cr-1-details

XRD YAML:

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xmycrs.example.org
spec:
  group: example.org
  names:
    kind: XMyCr
    plural: xmycrs
  claimNames:
    kind: MyCr
    plural: mycrs
  connectionSecretKeys:
  - hostname
  defaultCompositionRef:
    name: mycrcomposition
  versions:
  - name: v1alpha1
    served: true
    referenceable: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              parameters:
                type: object
                properties:
                  field2:
                    type: string
                required:
                - field2
            required:
            - parameters
          status:
            type: object
            properties:
              phase:
                description: Phase that the CR is in currently.
                type: string

Composition YAML

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: mycrcomposition
  labels:
    crossplane.io/xrd: xmycr.example.org
    crtype: myresource
spec:
  compositeTypeRef:
    apiVersion: example.org/v1alpha1
    kind: XMyCr
  writeConnectionSecretsToNamespace: crossplane-system
  resources:
  - name: myresource
    base:
      apiVersion: kubernetes.crossplane.io/v1alpha1
      kind: Object
      metadata:
        name: myresource-sample-3
        namespace: "default"
      spec:
        forProvider:
          manifest:
            apiVersion: example.com/v1
            kind: MyResource
            metadata:
              name: myresource-sample-3
              namespace: default
            spec:
              field1: "value1"
              field2: "value2"
            status:
              message: "Sample status message"
              phase: "Running"
        providerConfigRef:
          name: kubernetes-provider

    patches:
    - type: FromCompositeFieldPath
      fromFieldPath: spec.parameters.field2
      toFieldPath: spec.forProvider.manifest.spec.field2

      policy:
        fromFieldPath: Required
    readinessChecks:
    - type: None

Custom Resource Definition (CRD):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myresources.example.com
spec:
  group: example.com
  names:
    kind: MyResource
    singular: myresource
    plural: myresources
    shortNames:
      - mr
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      subresources:
        status: {}
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                field1:
                  type: string
                field2:
                  type: string
              required:
                - field1
                - field2
            status:
              type: object
              properties:
                message:
                  type: string
                phase:
                  type: string
              required:
                - message
                - phase

Custom resource created by kubenetes provider in target cluster:

apiVersion: example.com/v1
kind: MyResource
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: '{"apiVersion":"example.com/v1","kind":"MyResource","metadata":{"name":"myresource-sample-3","namespace":"default"},"spec":{"field1":"value1","field2":"sample
      field2"},"status":{"message":"Sample status message","phase":"Running"}}'
  creationTimestamp: "2023-04-14T05:57:03Z"
  generation: 1
  name: myresource-sample-3
  namespace: default
  resourceVersion: "1510249"
  uid: fb17b130-2f11-4388-89ee-39e1d0e23dc0
spec:
  field1: value1
  field2: sample field2

What environment did it happen in?

  • Crossplane version: v1.10.3

  • Kubernetes version :
    Crossplane Machine: v1.18.3
    Target Machine: v1.25.8

  • Kubernetes distribution:
    Crossplane Machine: Minikube
    Target Machine: Microk8s

  • OS (e.g. from /etc/os-release)
    Target Machine:
    NAME="Ubuntu"
    VERSION="20.04.5 LTS (Focal Fossa)"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 20.04.5 LTS"
    VERSION_ID="20.04"
    HOME_URL="https://www.ubuntu.com/"
    SUPPORT_URL="https://help.ubuntu.com/"
    BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
    PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
    VERSION_CODENAME=focal
    UBUNTU_CODENAME=focal
    Crossplane Machine:
    NAME="Ubuntu"
    VERSION="16.04.6 LTS (Xenial Xerus)"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 16.04.6 LTS"
    VERSION_ID="16.04"
    HOME_URL="http://www.ubuntu.com/"
    SUPPORT_URL="http://help.ubuntu.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
    VERSION_CODENAME=xenial
    UBUNTU_CODENAME=xenial

  • Kernel (e.g. uname -a)
    Crossplane Machine: Linux sm-dev 4.4.0-124-generic #148-Ubuntu SMP Wed May 2 13:00:18 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
    Target Machine: Linux dev-machine 5.4.0-131-generic #147-Ubuntu SMP Fri Oct 14 17:07:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

I believe this is the correct/expected behavior by provider-kubernetes. Typical kubernetes workflow has the user (provider-kubernetes in this case) provide the spec contents for the custom resource, which is the desired state, and the controller for the custom resource provides the status content, which is the actual state. The user does not typically provide status content, as the controller will override it with what it observes the actual state to be.

@bobh66 Thanks for the comment.

The scenario here is the custom resource(apiVersion: example.com/v1, kind: MyResource) status and spec are being updated by my custom controller (lets say controller1) and this happens in k8s cluster1 (it is not the user who is updating the spec or status). Once controller1 updates the spec and status, I am using Crossplane provider-kubernetes to push this CR from k8s cluster1 to k8s cluster2. It is expected that the provider-kubernetes creates the custom resource as in cluster2, but this is not the case here. provider-kubernetes only updates the spec section but not the status section of the custom resource in cluster2.

I assume that the MyResource controller in cluster2 (controller2) requires status information from the MyResource in cluster1, for example in a main/backup distributed application pair where the backup needs to know that the main instance is running.

You could create a MyResourceStatus resource in cluster2 with all of the information needed from MyResource cluster1 in the spec, or you could add a "remoteStatus" field in the MyResource spec and populate it with the status of the resource in cluster1 when you create it in cluster2. That would allow the controllers to maintain the status fields while still passing the status information to the remote instance.

Yes the controller2 needs the MyResource status from cluster1.

Yes I could populate it in the spec section of the CR in cluster1 and create the status in cluster2 using that info, but this would be kind of a work around/redundant field. It would be better if the provider-kubernetes could just create the CR in cluster2 with the status I pass from cluster1 in addition to spec creation. This would be a proper workflow rather than we having to do it ourselves through code.

I believe it should be feasible as it is just another call to k8s API server to update the status(in addition to the API call to update spec section).

If status.phase represents the status of the resource in cluster1 (it's an output), why doesn't it represent the status of the resource instance that is deployed in cluster2? It seems like status.phase is being treated as both an output in cluster1 and an input in cluster2? How does the controller in cluster2 know to use status.phase as an input? What happens when the controller in cluster2 overwrites status.phase with the actual status of the MyResource instance in cluster2? If this scenario was supported provider-kubernetes would be fighting with the MyResource controller in cluster2 for control of the status.phase field.

I think it makes more sense to treat status.phase consistently as an output all of the time, and define a new input to transfer the remote status as an input to the second instance.

MyResource is registered both with controller1 and controller2, so they are aware of the MyResource object and know how to process/manage it. Controller1 (in cluster1) is responsible for processing status.Phase=PHASE1 and status.Phase=PHASE2 of the MyResource CR and controllers is responsible for processing the status.Phase=PHASE3 and status.Phase=PHASE4 statuses. So, yes the output of cluster1 (status.Phase=PHASE2) acts as an input to cluster2 and controller2 is responsible for taking the MyResource further from that state (status.Phase=PHASE2).

What happens when the controller in cluster2 overwrites status.phase with the actual status of the MyResource instance in cluster2?

It does not matter. That is the expected behavior we are looking for. As mentioned earlier controller1 is responsible for PHASE1 and PHASE2 and controller2 is responsible for PHASE3 and PHASE4 of MyResource, so there is no overlap or race condition possibility as such.

If this scenario was supported provider-kubernetes would be fighting with the MyResource controller in cluster2 for control of the status.phase field.

Not really. What I am looking for is not provider-kubernetes to monitor/manage/observe the status field of MyResource, I agree that it is not the responsibility of provider-kubernetes to do that. What I want is provider-kubernetes along with creating the CR(with the spec field), it must also create the status field of the object(same status that is mentioned in composition, it does not have to create any new status, just apply the spec and status fields mentioned in the managed resource in compsition)

Any update on this?

I actually see other instances where this would be useful.

Consider examples whereby crossplane is used to reflect external resources back into the cluster for consumption by others but not as a resource to be consumed directly by the origin controller.

One example of this would be ClusterAPI and the cluster.x-k8s.io/managed-by annotation which states:

It can be applied to InfraCluster resources to signify that some external system is managing the cluster infrastructure. Provider InfraCluster controllers will ignore resources with this annotation. An external controller must fulfill the contract of the InfraCluster resource. External infrastructure providers should ensure that the annotation, once set, cannot be removed.

Furthermore, the proposed contract of the cluster.x-k8s.io/managed-by annotation goes on to clarify:

The external management system must:

  • Populate all required fields within the InfraCluster spec to allow other CAPI components to continue as normal.
  • Adhere to all Cluster API contracts for infrastructure providers.
  • When the infrastructure is ready, set the appropriate status as is done by the provider controller today.

https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20210203-externally-managed-cluster-infrastructure.md#implementation-detailsnotesconstraints

Within this, the last point is pertinent.

  • When the infrastructure is ready, set the appropriate status as is done by the provider controller today.

In the use-case I am applying, crossplane becomes that external management system, but it in itself does not create CAPI resources so to achieve this, I'm creating the resources with this provider but in difference to (how I understand) the OPs issue, I explicitly require crossplane to manage the status; no other controller should be able to operate upon it unless I first remove control from crossplane.