Introduction à Crossplane

Crossplane permet de déployer des ressources d’infrastructure en se connectant aux APIs de différents providers.

Ces providers peuvent être des solutions cloud comme AWS, GCP, Azure, ou IBM Cloud, mais aussi des providers plus divers comme GitLab, Helm, ou autres… A ce jour, un peu moins d’une vingtaine de providers est utilisable.

Crossplane se pose comme une alternative à Terraform, avec toutefois les particularités suivantes :

  • Crossplane spécifie les ressources à créer dans des manifestes Kubernetes,
  • Crossplane gère la réconciliation en continu, en synchronisant en temps réel l’état réel avec l’état souhaité.

Dans cet article, nous allons mettre en place Crossplane et déployer quelques ressources sur Google Cloud Platform (GCP).

Avec Crossplane la description des ressources d’infrastructure s’effectue via des manifestes Kubernetes. La première étape consiste donc à déployer un cluster Kubernetes, puis installer Crossplane sur ce cluster.

Création du cluster k8s

Commençons donc par créer un cluster Kubernetes. Par souci de simplicité, nous déployons ce cluster sur un poste local avec la solution Kind :

kind create cluster --name crossplane --image kindest/node:v1.22.4 --wait 5m

kubectl cluster-info --context kind-crossplane

Mise en place de Crossplane

Déploiement de Crossplane sur le cluster Kubernetes

Nous déployons ensuite Crossplane à l’aide d’un chart Helm dans un namespace spécifique sur le cluster Kubernetes :

kubectl create namespace crossplane-system
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
helm list -n crossplane-system

kubectl get all -n crossplane-system

Installation de la CLI crossplane en local

Crossplane propose également un client à installer sur le poste local :

curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh

./kubectl-crossplane --help

Configuration du provider cible

La notion de provider dans Crossplane est la même que dans Terraform.

Dans l’exemple ci-dessous nous allons déployer des ressources sur le provider GCP.

Installation des CRDs pour GCP

Nous installons tout d’abord les CRDs (Custom Resources Definitions) qui permettront de définir les types de ressources GCP que Crossplane sera capable de manipuler.

./kubectl-crossplane install configuration registry.upbound.io/xp/getting-started-with-gcp:latest

Vérification de l’installation :

kubectl get configuration
kubectl get pkg

Création d’un secret pour utiliser le provider

Crossplane se connecte à l’API du provider en utilisant un secret Kubernetes. Dans le cas de GCP, il faut tout d’abord créer un service account GCP avec les droits suffisants pour pouvoir déployer des ressources GCP, puis générer une clé pour ce service account.

Nous pouvons ensuite créer un secret kubernetes avec le contenu de cette clé :

kubectl create secret generic gcp-creds -n crossplane-system --from-file=creds=./gcp-credentials.json

Configuration du provider

Le fichier gcp-providerconfig.yaml va nous permettre de déclarer la configuration pour le provider :

apiVersion: gcp.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
  name: gcp
spec:
  projectID: <my-project-id>
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: gcp-creds
      key: creds

On fait manger le fichier à Kubernetes :

kubectl apply -f gcp-providerconfig.yaml 

Vérification :

kubectl get providerconfig

Déploiement de ressources avec le provider GCP officiel

Maintenant que Crossplane est installé et que le provider GCP est configuré, nous pouvons tenter de déployer des ressources sur GCP.

La liste des ressources qu’il est possible de déployer avec le provider GCP officiel est disponible ici : https://doc.crds.dev/github.com/crossplane/provider-gcp@v0.21.0

Nous allons par exemple créer un bucket, en ajoutant le fichier my-crossplane-bucket.yaml avec le contenu suivant :

apiVersion: storage.gcp.crossplane.io/v1alpha3
kind: Bucket
metadata:
  name: my-crossplane-bucket
  labels:
    example: "true"
  annotations:
    crossplane.io/external-name: crossplane-4g5f6
spec:
  location: EU
  storageClass: MULTI_REGIONAL
  providerConfigRef:
    name: gcp 
  deletionPolicy: Delete

Vérification de l’état de la ressource my-crossplane-bucket :

kubectl get bucket my-crossplane-bucket 
NAME                   READY   SYNCED   STORAGE_CLASS    LOCATION   AGE
my-crossplane-bucket   True    True     MULTI_REGIONAL   EU         15m

Elle semble correctement synchronisée (SYNCED = True) et disponible (READY = True).

Vérifions côté GCP que le bucket est bien présent :

gcloud alpha storage ls
gs://crossplane-4g5f6/

Déploiement de ressources avec le provider GCP Jet

Le nombre de type de ressources actuellement déployable avec le provider GCP officiel (v0.21.0) est plutôt faible : 28. En effet l’ajout de nouveaux types de ressources est particulièrement couteux en temps de développement. Ceci est assez problématique, et on ne peut par exemple à ce jour même pas déployer une instance de VM avec ce provider !

Pour palier ce problème, la société Upbound travaille sur le projet Tarrajet. L’idée derrière Terrajet est de s’appuyer sur le travail énorme effectué ces dernières années par les équipes qui développent la solution Terraform pour générer automatiquement les CRDs Crossplane pour toutes les ressources que Terraform est capable de gérer.

Note du 09 janvier 2023 : attention Terrajet est désormais déprécié au profit de Upjet.

La page suivante présente la liste des providers “Jet” disponibles :
https://github.com/crossplane/crossplane/blob/master/docs/concepts/providers.md

En installant le provider GCP Jet, on passe de 28 types de ressources utilisables à 438 ! La liste est disponible là :
https://doc.crds.dev/github.com/crossplane-contrib/provider-jet-gcp@v0.2.0-preview

Installation des CRDs pour GCP Jet

./kubectl-crossplane install provider crossplane/provider-jet-gcp:v0.1.0

Vérification de l’installation :

kubectl get configuration
kubectl get pkg

Configuration du provider GCP Jet

Le nouveau fichier gcp-jet-providerconfig.yaml va nous permettre de déclarer le nouveau provider Jet :

apiVersion: gcp.jet.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
  name: gcp-jet
spec:
  projectID: <my-project-id>
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: gcp-creds
      key: creds

On référence le même secret que pour le provider GCP classique.

On fait manger le fichier à Kubernetes :

kubectl apply -f gcp-jet-providerconfig.yaml 

Déploiement d’une instance de VM

Essayons maintenant de déployer une instance de VM…

La spécification de l’objet instance est disponible ici :
https://doc.crds.dev/github.com/crossplane-contrib/provider-jet-gcp/compute.gcp.jet.crossplane.io/Instance/v1alpha2@v0.2.0-preview

Créons le fichier my-crossplane-instance.yaml avec le contenu suivant :

apiVersion: compute.gcp.jet.crossplane.io/v1alpha1
kind: Instance
metadata:
  name: my-crossplane-instance
spec:
  providerConfigRef:
    name: gcp-jet
  forProvider:
    machineType: e2-small
    zone: europe-west1-b
    labels:
      managed-by: crossplane
    networkInterface:
      - network: default
    bootDisk:
      - initializeParams:
          - image: debian-cloud/debian-10

Vérification de l’état de la resource my-crossplane-instance :

kubectl get instances
NAME                     READY   SYNCED   EXTERNAL-NAME            AGE
my-crossplane-instance   True    True     my-crossplane-instance   8m6s

Vérification de la présence de l’instance sur GCP :

gcloud compute instances list
NAME                    ZONE            MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP  STATUS
my-crossplane-instance  europe-west1-b  e2-small                   10.132.0.36               RUNNING

Pour détruire l’instance, rien de plus simple, il suffit de supprimer la ressource my-crossplane-instance du cluster Kubernetes :

$ kubectl delete -f my-crossplane-instance.yaml 
instance.compute.gcp.jet.crossplane.io "my-crossplane-instance" deleted

Sur cet autre exemple, nous allons pousser deux clés ssh et un script d’init sur la VM à créer. Nous allons aussi lui associer une adresse IP publique. Modifions le fichier my-crossplane-instance.yaml avec le contenu suivant :

apiVersion: compute.gcp.jet.crossplane.io/v1alpha1
kind: Instance
metadata:
  name: my-crossplane-instance
spec:
  providerConfigRef:
    name: gcp-jet
  forProvider:
    machineType: e2-small
    zone: europe-west1-b
    labels:
      managed-by: crossplane
    networkInterface:
      - network: default
        accessConfig:
          - {}      
    bootDisk:
      - initializeParams:
          - image: debian-cloud/debian-10
    metadata:
      ssh-keys: |-
          user1:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAEAQDJ6TgFfAPfgQR8X9E6i user1
          user2:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCmSIZRMBampHYZ7y9mV user2          
    # La taille maximum pour metadataStartupScript est 256 Ko
    metadataStartupScript: |
      echo 'test' > /test.txt
      echo $(date) >> /test.txt      

Créons la ressource :

kubectl apply -f my-crossplane-instance.yaml 
instance.compute.gcp.jet.crossplane.io/my-crossplane-instance created

kubectl get instances
NAME                     READY   SYNCED   EXTERNAL-NAME            AGE
my-crossplane-instance   True    True     my-crossplane-instance   15m

Et vérifions que le script a bien été exécuté au lancement de la VM :

ssh user1@IP-PUBLIQUE
sudo cat /test.txt
test
Thu May 5 15:34:37 UTC 2022

Composition

Crossplane permet d’aller encore bien plus loin avec la notion de Composition, à découvrir dans la vidéo ci-dessous…

https://www.youtube.com/watch?v=AtbS1u2j7po