Skip to main content

Recursos Gestionados (Managed Resources)

Un recurso gestionado representa cualquier recurso externo provisionado y controlado por un provider de Crossplane. Recursos externos, como aquellos en proveedores de nube, son mapeados a recursos gestionados dentro de Kubernetes, permitiendo su gestión declarativa a través de objetos nativos de Kubernetes.

Crossplane ofrece dos enfoques para trabajar con recursos gestionados:

  • Recursos individuales: Crear y gestionar recursos gestionados individuales directamente.
  • Compositions (Composiciones): Definir conjuntos de recursos gestionados que son provisionados y gestionados como una única unidad lógica.

El propio equipo de Crossplane recomienda comenzar trabajando con recursos individuales para comprender mejor el funcionamiento del sistema y, a medida que ganes más experiencia, avanzar para el uso de composiciones.

Todo recurso gestionado posee una política de eliminación asociada que define el comportamiento cuando el recurso gestionado es eliminado:

  • deletionPolicy: Delete (Por defecto - Elimina el recurso externo al eliminar el recurso gestionado)
  • deletionPolicy: Orphan (Deja el recurso externo al eliminar el recurso gestionado)

forProvider

Mirando el yaml abajo que define un ec2 en aws.

apiVersion: ec2.aws.upbound.io/v1beta1
kind: Instance
metadata:
name: crossplane-test
spec:
# El spec.forProvider de un recurso gestionado es mapeado a los parámetros del recurso externo.
# El forProvider es considerado la fuente de la verdad. Después del recurso creado en el provider, si un usuario hace una alteración dentro del console de Web de un proveedor, Crossplane revertirá ese cambio de vuelta a lo que está configurado en el forProvider.
forProvider:
region: us-east-1
ami: ami-02a89066c48741345
subnetId: subnet-0a056609984bfbb94
instanceType: t2.micro
providerConfigRef:
name: default

Muchas veces un resource necesita referenciarse a otro resource como por ejemplo el subnetId que definimos arriba. Este recurso puede ser hecho manualmente o por inferencia.

Una vez que instalamos el provider aws upbound que usa apiversion de upbound, vamos a utilizar el github de ellos https://github.com/upbound/provider-aws/tree/main para algunos ejemplos

Vamos a crear una VPC y después una subnet usando esta VPC.

apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
metadata:
name: crossplane-vpc
spec:
forProvider:
region: us-east-1
cidrBlock: 10.0.0.0/16
enableDnsSupport: true
providerConfigRef:
name: default

Vamos a aplicar el manifiesto arriba y verificar


kubectl apply -f vpc.yaml
vpc.ec2.aws.upbound.io/crossplane-vpc created

kubectl get vpcs.ec2.aws.upbound.io
NAME READY SYNCED EXTERNAL-NAME AGE
crossplane-vpc True True vpc-0bbe67dde157bb143 15m

# Haz un describe y mira lo que tenemos
kubectl describe vpcs.ec2.aws.upbound.io crossplane-vpc

Lo que nos importaba allí era tener una VPC para crear una subnet referenciando por su nombre que será automáticamente sustituido por su external name, observa.

apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
metadata:
name: crossplane-subnet1
spec:
forProvider:
region: us-east-1
availabilityZone: us-east-1a
# Aquí utilizamos la referencia por el nombre, que nos traerá el ID
vpcIdRef:
name: crossplane-vpc
cidrBlock: 10.0.10.0/24
providerConfigRef:
name: default
---
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
metadata:
name: crossplane-subnet2
spec:
forProvider:
region: us-east-1
availabilityZone: us-east-1b
# Aquí utilizamos directo el ID sin hacer referencia
vpcId: vpc-0647cd3d73b2245b5
cidrBlock: 10.0.20.0/24
providerConfigRef:
name: default
---
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
metadata:
name: crossplane-subnet3
spec:
forProvider:
region: us-east-1
availabilityZone: us-east-1b
# Aquí seleccionamos usando labels
vpcIdSelector:
matchLabels:
name: crossplane-vpc
cidrBlock: 10.0.30.0/24
providerConfigRef:
name: default
---

Y vamos a aplicar...

❯ kubectl get subnets.ec2.aws.upbound.io
NAME READY SYNCED EXTERNAL-NAME AGE
crossplane-subnet1 True True subnet-021e79c108d979bee 6m10s
crossplane-subnet2 True True subnet-06194392f1196f39e 86s

Entendimos las referencias, ¿pero observaron que siempre necesitamos definir el bloque providerConfigRef?

Si hacemos una única composición creando las VPCs y las subnets en un único manifiesto, podemos tener solamente un providerConfigRef.

¿Qué sucedería si intentásemos eliminar una VPC, que tiene una subnet dentro? Recuerda que en el propio console de AWS, no sería posible eliminar sin antes remover las subnets.

¿Y si cambiásemos la región de una subnet? No conseguiría aplicar, pues ese campo es inmutable.

Crossplane trata el recurso gestionado como la fuente de la verdad por defecto; espera tener todos los valores, spec.forProvider incluyendo los opcionales. Si no es proporcionado, Crossplane rellenará los campos vacíos con los valores asignados por el proveedor. Por ejemplo, considera campos como region y availabilityZone. Puedes especificar apenas la región y dejar que el proveedor de nube elija la zona de disponibilidad. En ese caso, si el proveedor asigna una zona de disponibilidad, Crossplane usará ese valor para rellenar el spec.forProvider.availabilityZone campo.

Annotations

Algunas anotaciones son creadas por el propio crossplane en los recursos.

AnotaciónDefinición
crossplane.io/external-nameEl nombre del recurso gestionado dentro del Provider. Por defecto el nombre dado es el mismo valor del name en metadata, pero es posible tener un nombre específico. Es buena práctica mantener el mismo.
crossplane.io/external-create-pendingCreado automáticamente la fecha/hora de cuando Crossplane comenzó a crear el recurso gestionado.
crossplane.io/external-create-succeededCreado automáticamente la fecha/hora de cuando el Provider creó con éxito el recurso gestionado.
crossplane.io/external-create-failedCreado automáticamente de fecha/hora de cuando el Provider no consiguió crear el recurso gestionado.
crossplane.io/pausedIndica que Crossplane no está reconciliando este recurso. Lee la anotación de pausa para obtener más detalles.
crossplane.io/composition-resource-namePara recursos gestionados creados por una Composition, ese es el resources.name valor de la Composition.

Estas annotations son creadas automáticamente caso sucedan.

Creando un clúster RDS por ejemplo en AWS, sin instancia, podemos observar el uso de la annotation para exponer el nombre que queremos. Este ejemplo está en la carpeta resource para ser aplicado.

apiVersion: rds.aws.upbound.io/v1beta1
kind: Cluster
metadata:
# Este sería el nombre si no tuviésemos usado la annotation abajo
name: clusterdb
annotations:
crossplane.io/external-name: cluster-db-custom-name
spec:
forProvider:
region: us-east-1
engine: aurora-postgresql
masterUsername: cpadmin
masterPasswordSecretRef:
name: clusterdb-password
namespace: crossplane-system
key: password
skipFinalSnapshot: true
writeConnectionSecretToRef:
name: rds-clusterdb-secret
namespace: crossplane-system
---
apiVersion: v1
kind: Secret
metadata:
name: clusterdb-password
namespace: crossplane-system
type: Opaque
stringData:
password: TestPass0!
kubectl apply -f rds.yaml

kubectl get cluster.rds
NAME READY SYNCED EXTERNAL-NAME AGE
clusterdb True True cluster-db-custom-name 4m7s

kubectl describe cluster.rds clusterdb
Name: clusterdb
Namespace:
Labels: <none>
## Mira las annotations. Como no falló no tiene la annotation failed ni paused.
Annotations: crossplane.io/external-create-pending: 2023-10-24T13:53:58Z
crossplane.io/external-create-succeeded: 2023-10-24T13:53:58Z
crossplane.io/external-name: cluster-db-custom-name
upjet.crossplane.io/provider-meta:
{"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0":{"create":7200000000000,"delete":7200000000000,"update":7200000000000}}
...

rds

Es crítico entender primero cómo funcionan los recursos gestionados, pero como dije anteriormente, el uso de compositions que veremos más adelante es una manera mucho mejor de definir y gestionar los recursos.

providerConfigRef

El providerConfigRef informa al Provider cuál ProviderConfig usar al crear el recurso gestionado. Pasamos el nombre de la configuración.

Cada recurso gestionado puede hacer referencia a diferentes ProviderConfigs. Esto permite que diferentes recursos gestionados sean autenticados con credenciales diferentes en el mismo Provider.

Drift y Reconciliación

¿Qué sucede si eliminamos un pod en Kubernetes que es controlado por un replicaset? Lo recreará. Garantiza el estado deseado ¿verdad? Cuida para que el pod esté siempre en pie, en la cantidad correcta.

Crossplane es un controller ¿verdad? Entonces, si un recurso es eliminado de la cloud, pero está definido en Crossplane, lo recreará. Verá que el recurso que tiene que provisionar no se encuentra más y aplicará nuevamente. En Terraform, caso esto suceda solamente será detectada la diferencia la próxima vez que sea aplicado el proyecto.

Crossplane elevó el concepto de reconciliación y drift para mucho más allá de los pods, ¡sino para todo!

En realidad cuando aplicamos varios manifiestos de una única vez Crossplane ejecuta todos al mismo tiempo, no es igual a Terraform con el depends_on. Aplicará y a medida que quede true, las referencias serán corregidas automáticamente.

ver los recursos gestionados

kubectl get managed
NAME READY SYNCED EXTERNAL-NAME AGE
kafkatopic.confluent.crossplane.io/emx-test-composition-zn79p-w6wxd True True lkc-7nx291/emx_test-composition 3d16h
kafkatopic.confluent.crossplane.io/topic-emx-ninjas-test True True lkc-7nx291/emx_test-crossplane-topic-emx 11d
kafkatopic.confluent.crossplane.io/topic-emx-ninjas-test2 True True lkc-7nx291/emx_test-crossplane-topic-emx2 11d

NAME READY SYNCED AGE PATH WITH NAMESPACE
project.projects.gitlab.crossplane.io/test-project-crossplane True True 6d14h latamairlines/oper/emx/platform-engineering/port/test