--- title: "Crossplane en Kubernetes: instalar y crear composiciones" description: "Instala Crossplane v2 con Helm, define una API propia con un XRD y una Composition, y gestiona recursos cloud como objetos de Kubernetes con AWS." category: "Ingeniería de Plataformas" language: es source: https://cubepath.com/es/docs/ingeniería-de-plataformas/crossplane-gestion-de-infraestructura updated: 2026-09-25 --- # Crossplane en Kubernetes: instalar y crear composiciones Crossplane convierte un clúster de Kubernetes en un plano de control para infraestructura: defines APIs propias (por ejemplo, "una aplicación" o "una base de datos") y Crossplane crea y mantiene los recursos que hay detrás, ya sean objetos de Kubernetes o servicios de un proveedor cloud. En este tutorial instalarás Crossplane v2 con Helm, crearás una API `App` que genera un `Deployment` y un `Service` mediante una Composition, y opcionalmente gestionarás un bucket de S3 con el proveedor de AWS. ## Requisitos previos - Un clúster de Kubernetes con una versión soportada y al menos 2 GB de RAM libres. Sirve un clúster de un solo nodo con k3s en un VPS de CubePath con Ubuntu 24.04. - `kubectl` configurado contra ese clúster, con permisos de administrador. - Helm 3.2 o posterior instalado en la máquina desde la que trabajas. - Solo para el paso 7: una cuenta de AWS y un par de claves de acceso con permiso para crear buckets de S3. Comprueba que tienes acceso al clúster: ```bash kubectl get nodes ``` ```output NAME STATUS ROLES AGE VERSION cp-node Ready control-plane,master 3d v1.33.x+k3s1 ``` ## Conceptos básicos Antes de empezar, conviene tener claros los términos que usa Crossplane: | Término | Qué es | |---------|--------| | XRD (CompositeResourceDefinition) | Define el esquema de una API nueva, como `App` | | XR (composite resource) | Cada objeto creado con esa API, como `my-app` | | Composition | Indica qué recursos crear para cada XR y cómo rellenarlos | | Function | Módulo que ejecuta la lógica de la Composition (YAML, plantillas, Python...) | | Provider | Paquete que añade recursos gestionados de un servicio externo, como AWS S3 | | Managed resource (MR) | Un recurso externo representado en Kubernetes, como un `Bucket` | ## Paso 1: Instalar Crossplane con Helm Añade el repositorio estable de Crossplane y actualiza el índice: ```bash helm repo add crossplane-stable https://charts.crossplane.io/stable helm repo update ``` Instala Crossplane en su propio namespace: ```bash helm install crossplane \ --namespace crossplane-system \ --create-namespace \ crossplane-stable/crossplane ``` Comprueba que los pods están en marcha: ```bash kubectl get pods -n crossplane-system ``` ```output NAME READY STATUS RESTARTS AGE crossplane-6d67f8cd9d-g2gjw 1/1 Running 0 45s crossplane-rbac-manager-86d9b5cf9f-2vc4s 1/1 Running 0 45s ``` Crossplane también ha registrado sus tipos en la API de Kubernetes: ```bash kubectl api-resources | grep -E 'compositeresourcedefinitions|compositions|functions|providers' ``` La salida debe incluir `compositeresourcedefinitions`, `compositions`, `functions` y `providers`. ## Paso 2: Definir la API con un XRD Vas a crear un tipo nuevo, `App`, con un único campo obligatorio: la imagen del contenedor. En Crossplane v2 los XR pueden ser de namespace (`scope: Namespaced`), igual que un `Deployment`. Crea el archivo: ```bash nano xrd.yaml ``` ```yaml apiVersion: apiextensions.crossplane.io/v2 kind: CompositeResourceDefinition metadata: name: apps.example.crossplane.io spec: scope: Namespaced group: example.crossplane.io names: kind: App plural: apps versions: - name: v1 served: true referenceable: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: description: Imagen OCI del contenedor de la aplicación. type: string required: - image status: type: object properties: replicas: description: Número de réplicas disponibles. type: integer address: description: IP interna del servicio. type: string ``` El nombre del XRD debe ser `.`. Aplícalo y comprueba que Kubernetes ya sirve la nueva API: ```bash kubectl apply -f xrd.yaml kubectl get xrd ``` ```output NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 12s ``` ## Paso 3: Instalar la función patch-and-transform Crossplane no sabe todavía qué hacer al crear un `App`. Esa lógica la ejecutan las funciones de composición. La función `patch-and-transform` permite describir los recursos en YAML y copiar valores del XR a ellos. Crea el archivo: ```bash nano function.yaml ``` ```yaml apiVersion: pkg.crossplane.io/v1 kind: Function metadata: name: crossplane-contrib-function-patch-and-transform spec: package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 ``` Aplícalo y espera a que el paquete esté instalado y sano: ```bash kubectl apply -f function.yaml kubectl get functions ``` ```output NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 30s ``` > Nota: consulta las [versiones publicadas](https://github.com/crossplane-contrib/function-patch-and-transform/releases) de la función y usa la más reciente si lo prefieres. ## Paso 4: Crear la Composition La Composition indica que cada `App` se compone de un `Deployment` y un `Service`. Los parches copian el nombre del XR a las etiquetas y la imagen al contenedor, y devuelven al `status` del XR las réplicas disponibles y la IP del servicio. Crea el archivo: ```bash nano composition.yaml ``` ```yaml apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: name: app-yaml spec: compositeTypeRef: apiVersion: example.crossplane.io/v1 kind: App mode: Pipeline pipeline: - step: create-deployment-and-service functionRef: name: crossplane-contrib-function-patch-and-transform input: apiVersion: pt.fn.crossplane.io/v1beta1 kind: Resources resources: - name: deployment base: apiVersion: apps/v1 kind: Deployment spec: replicas: 2 template: spec: containers: - name: app ports: - containerPort: 80 patches: - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: metadata.labels[example.crossplane.io/app] - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - type: FromCompositeFieldPath fromFieldPath: spec.image toFieldPath: spec.template.spec.containers[0].image - type: ToCompositeFieldPath fromFieldPath: status.availableReplicas toFieldPath: status.replicas readinessChecks: - type: MatchCondition matchCondition: type: Available status: "True" - name: service base: apiVersion: v1 kind: Service spec: ports: - protocol: TCP port: 8080 targetPort: 80 patches: - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: metadata.labels[example.crossplane.io/app] - type: FromCompositeFieldPath fromFieldPath: metadata.name toFieldPath: spec.selector[example.crossplane.io/app] - type: ToCompositeFieldPath fromFieldPath: spec.clusterIP toFieldPath: status.address readinessChecks: - type: NonEmpty fieldPath: spec.clusterIP ``` Los `readinessChecks` le dicen a Crossplane cuándo considerar listo cada recurso: el `Deployment` cuando su condición `Available` es `True`, y el `Service` cuando tiene IP asignada. Aplica la Composition: ```bash kubectl apply -f composition.yaml kubectl get compositions ``` ```output NAME XR-KIND XR-APIVERSION AGE app-yaml App example.crossplane.io/v1 8s ``` ## Paso 5: Crear una App y comprobar el resultado Ahora cualquier usuario del clúster puede pedir una aplicación con cuatro líneas de YAML, sin conocer los detalles del `Deployment` ni del `Service`: ```bash nano app.yaml ``` ```yaml apiVersion: example.crossplane.io/v1 kind: App metadata: namespace: default name: my-app spec: image: nginx ``` Aplícalo y espera a que esté listo: ```bash kubectl apply -f app.yaml kubectl get app my-app ``` ```output NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 40s ``` `SYNCED` indica que Crossplane ha aplicado la Composition sin errores, y `READY` que todos los recursos compuestos han pasado sus comprobaciones. Mira los recursos que ha creado: ```bash kubectl get deploy,service -l example.crossplane.io/app=my-app ``` ```output NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-app-2r2rk 2/2 2 2 45s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-app-xfkzg ClusterIP 10.43.148.56 8080/TCP 45s ``` Y comprueba que el `status` del `App` recoge los datos devueltos por los parches: ```bash kubectl get app my-app -o jsonpath='{.status}{"\n"}' ``` ```output {"address":"10.43.148.56","conditions":[...],"replicas":2} ``` ## Paso 6: Actualizar y borrar la App Crossplane reconcilia de forma continua. Cambia la imagen en `app.yaml` a `nginx:alpine` y vuelve a aplicarlo: ```bash kubectl apply -f app.yaml kubectl get deploy -l example.crossplane.io/app=my-app -o jsonpath='{.items[0].spec.template.spec.containers[0].image}{"\n"}' ``` ```output nginx:alpine ``` Si alguien edita o borra a mano el `Deployment`, Crossplane lo devuelve al estado que marca la Composition. Al borrar el `App`, Crossplane elimina también los recursos compuestos: ```bash kubectl delete -f app.yaml kubectl get deploy,service -l example.crossplane.io/app=my-app ``` ```output No resources found in default namespace. ``` ## Paso 7: Gestionar un bucket de S3 con el proveedor de AWS (opcional) Los proveedores añaden recursos gestionados que representan servicios externos. Instala el proveedor de AWS S3: ```bash nano provider.yaml ``` ```yaml apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: name: crossplane-contrib-provider-aws-s3 spec: package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 ``` ```bash kubectl apply -f provider.yaml kubectl get providers ``` ```output NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-provider-aws-s3 True True xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 60s crossplane-contrib-provider-family-aws True True xpkg.crossplane.io/crossplane-contrib/provider-family-aws:v2.0.0 55s ``` El proveedor de S3 instala automáticamente `provider-family-aws`, que gestiona la autenticación de todos los proveedores de AWS. Guarda tus claves en un archivo temporal: ```bash nano aws-credentials.ini ``` ```ini [default] aws_access_key_id = your_access_key_id aws_secret_access_key = your_secret_access_key ``` Crea un Secret con ellas y borra el archivo: ```bash kubectl create secret generic aws-secret \ --namespace=crossplane-system \ --from-file=creds=./aws-credentials.ini shred -u aws-credentials.ini ``` Indica al proveedor que use ese Secret con una `ClusterProviderConfig`, válida para recursos de todos los namespaces: ```bash nano providerconfig.yaml ``` ```yaml apiVersion: aws.m.upbound.io/v1beta1 kind: ClusterProviderConfig metadata: name: default spec: credentials: source: Secret secretRef: namespace: crossplane-system name: aws-secret key: creds ``` ```bash kubectl apply -f providerconfig.yaml ``` Crea un bucket. Los nombres de S3 son únicos a nivel global, así que `generateName` añade un sufijo aleatorio: ```bash nano bucket.yaml ``` ```yaml apiVersion: s3.aws.m.upbound.io/v1beta1 kind: Bucket metadata: namespace: default generateName: crossplane-bucket- spec: forProvider: region: eu-west-1 ``` Usa `kubectl create` en lugar de `apply`, porque `generateName` no funciona con `apply`: ```bash kubectl create -f bucket.yaml kubectl get buckets.s3.aws.m.upbound.io ``` ```output NAME SYNCED READY EXTERNAL-NAME AGE crossplane-bucket-7tfcj True True crossplane-bucket-7tfcj 2m ``` Cuando `SYNCED` y `READY` valen `True`, el bucket existe en AWS. Bórralo con `kubectl delete buckets.s3.aws.m.upbound.io crossplane-bucket-7tfcj` (usa el nombre que te haya salido) y Crossplane lo eliminará también de AWS. > Importante: borra los recursos gestionados antes de desinstalar el proveedor o Crossplane. Si el control plane desaparece, nadie limpia los recursos externos y tendrás que borrarlos a mano en la consola del proveedor. ## Solución de problemas **El XR se queda con `SYNCED` en `False`.** Revisa los eventos y las condiciones del objeto. Suelen indicar un error en un parche o que la función no está disponible: ```bash kubectl describe app my-app ``` **Una función o un proveedor no llega a `HEALTHY`.** Revisa la descripción del paquete y los pods de `crossplane-system`: ```bash kubectl describe function crossplane-contrib-function-patch-and-transform kubectl get pods -n crossplane-system ``` **El bucket se queda con `READY` en `False`.** Casi siempre son las credenciales o los permisos de IAM. Los detalles aparecen en las condiciones del recurso: `kubectl describe buckets.s3.aws.m.upbound.io `. **Error de permisos al componer otro tipo de recurso.** Crossplane puede componer `Deployment` y `Service` sin configuración adicional, pero para otros tipos de Kubernetes puede necesitar permisos RBAC extra. Consulta la sección sobre acceso a recursos compuestos en la documentación de Compositions de Crossplane. ## Conclusión Has instalado Crossplane v2, definido una API `App` con un XRD, implementado su lógica con una Composition y la función patch-and-transform, y gestionado un recurso de AWS desde Kubernetes. Como siguientes pasos puedes añadir un recurso gestionado (por ejemplo el bucket) a la Composition de `App`, guardar XRD y Compositions en un repositorio Git sincronizado con Argo CD o Flux, o probar funciones con lógica más flexible como `function-go-templating` o `function-python`.