Avant de voir le fonctionnement de Crossplane, intéressons-nous au problème qu’il cible.
Tout d’abord un constat : aujourd’hui, Kubernetes devient le standard d'exécution pour les applications. Par contre, en ce qui concerne l'Infrastructure as Code, d’autres solutions sont utilisées : Terraform, Ansible ou un service spécifique à un cloud provider comme Cloud Formation dans le cas d’AWS. Nous avons donc une diversité de technologies à maîtriser selon le type de déploiement “applicatif” ou “infrastructure". Par maîtrise, nous entendons le Domain Specific Language (DSL), l’éco-système et enfin la communauté :
Qui plus est, la frontière entre “application” et “infrastructure” n’a plus de sens. Prenons le cas d’une application sur Kubernetes qui a besoin d’un bucket S3 : dans la majorité des cas, c’est mélanger un chart Helm pour l’application et du Terraform ou AWS CloudFormation pour le bucket S3 et sa configuration.
Mélange de technologies pour déployer un service
Pour terminer, Kubernetes est pensé pour être extensible au travers du pattern Operator. C'est-à-dire que des Custom Resource Definitions vont étendre les ressources que votre cluster peut gérer et un contrôleur est chargé de la réconciliation continue sur ces types de ressources. Rares sont les clusters qui ne font pas appel à des opérateurs comme External DNS, Cert-manager ou encore Prometheus Operator.
https://novakov-alexey.github.io/k8s-operator/
Pour résumer, nous avons d’un côté, des outils d’Infrastructure as Code qui supposent une montée en compétences pour bien les utiliser. De l’autre côté, Kubernetes devient de plus en plus le standard pour exécuter des applications. Ces dernières applications sont d’ailleurs souvent hybrides car mélangent des composants Kubernetes et des services cloud. Mélangeons tout cela et nous obtenons l’idée suivante : “quitte à utiliser Kubernetes comme socle d'exécution, étendons-le pour gérer les ressources cloud, voire toute l’infrastructure”.
C’est un framework basé sur des opérateurs pour orchestrer les applications et leurs infrastructures depuis Kubernetes. Sous licence Apache 2.0, Crossplane est principalement porté par Upbound qui a levé 60 millions de dollars en décembre 2021. Disponible en SaaS via upbound.io ou à installer en local sur vos clusters, le projet Crossplane est passé du statut CNCF sandbox à incubation à l’été 2021.
Pour la petite histoire, derrière upbound.io et donc la genèse de Crossplane se trouve une partie des créateurs de Rook (stockage cloud-native pour Kubernetes).
Installons Crossplane via son chart Helm :
kubectl create namespace crossplane-system
helm repo add crossplane-stable https://charts.crossplane.io/stable/
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
Comme avec Terraform, identifions le provider nous permettant de parler à l’infrastructure que nous souhaitons contrôler. Ces providers peuvent être officiels (AWS, GCP, Azure, AlibabaCloud, Rook ou Helm) ou communautaires (principalement via le répertoire git crossplane-contrib). Comme avec Terraform, ce sujet de providers est crucial puisque c’est ce qui va définir la liste des ressources que vous pourrez gérer.
Fin 2021, les providers officiels Crossplane pouvaient gérer 10 fois moins de ressources que les providers Terraform. Cet écart est maintenant comblé puisque le projet Terrajet annoncé en janvier 2022 génère automatiquement les providers Crossplane à partir de providers Terraform. Se pose alors la question des providers officiels : vont-ils être maintenus, supprimés ou intégrés eux-mêmes dans les providers Terrajet ?
10 fois moins de ressources pour les providers officiels, égalité entre Terraform et Terrajet
Fondamentalement, un provider Crossplane c’est un nouvel opérateur.
L’installation d’un provider se fait par un nouvel objet “pkg.crossplane.io/v1/Provider”. Par exemple, pour AWS :
Pour l’installation
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: crossplane-provider-aws
spec:
ignoreCrossplaneConstraints: false
package: crossplane/provider-aws:v0.23.0
packagePullPolicy: IfNotPresent
revisionActivationPolicy: Automatic
revisionHistoryLimit: 0
skipDependencyResolution: false
EOF
Ensuite, vous devez indiquer les identifiants que le provider doit utiliser. Le plus simple est d’indiquer un secret Kubernetes, c’est ce que nous indiquons ci-dessous. Pour plus de facilité, vous pouvez alimenter ces secrets à partir de sources chiffrées dans un répertoire git avec l’opérateur Bitnami Sealed Secrets. Pour aller plus loin, vous pouvez aussi utiliser une intégration avec Hashicorp Vault.
# Indicate your profile if you get multiple ones
AWS_PROFILE=default && echo -e "[default]\naws_access_key_id = $(aws configure get aws_access_key_id --profile $AWS_PROFILE)\naws_secret_access_key = $(aws configure get aws_secret_access_key --profile $AWS_PROFILE)" > creds.conf
# Then create the k8s
kubectl create secret generic aws-creds -n crossplane-system --from-file=key=./creds.conf
# And del the creds.conf file
rm -rf creds.conf
cat <<EOF | kubectl apply -f -
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: key
EOF
À ce stade, nous sommes prêts à déployer des ressources sur un compte AWS depuis Crossplane :
Les ressources Crossplane à déployer pour piloter une infra AWS
C’est l’heure de déclarer notre première instance de VPC AWS. Dans la terminologie Crossplane, on parle de “ressource managée” pour une instance de système externe gérée par un provider. Chaque type de ressource managée que propose un provider suit cette structure commune. Les champs les plus importants sont :
Exemple de ressource managée avec un VPC AWS
L’annotation external-name indique l’identifiant côté infrastructure distante. Sa valeur est automatiquement affectée lorsque la ressource est créée par un provider. Pour l’import dans Crossplane d’une ressource préexistante, nous devons indiquer son identifiant comme valeur de l’annotation lors de la création de l’objet Kubernetes.
En ce qui concerne les liens entre ressources, plusieurs mécanismes sont à notre disposition. Par exemple, dans le cas d’une ressource managée ec2.aws.crossplane.io/v1beta1/Subnet, le VPC au sein duquel le subnet doit être crée peut-être indiqué par :
On parle de dérive (drift) lorsque des ressources gérées par une solution d’infra as code, sont modifiées en dehors de cette solution ; par exemple depuis la console web du cloud provider. Ainsi, c’est lors de la prochaine invocation de la solution d’Infra as Code que les modifications seront annulées pour revenir à l’état désiré. Les habitués de Terraform ou AWS CloudFormation connaissent bien la notion de drift puisqu’entre deux invocations de nombreux changements ont pu avoir lieu. Le risque de drift peut être réduit par des invocations automatiques et régulières depuis une chaîne de déploiement continu par exemple, mais c’est assez rare dans les faits .
Avec Crossplane, le contrôleur de chaque provider effectue en permanence les boucles de réconciliation sur les ressources managées qu’il gère pour remédier aux modifications faites en dehors de Crossplane. Pour éviter les dépassements de quotas d’appels API sur un cloud provider, les providers Crossplane intègrent des rate limiter pour lisser les appels.
Les boucles de réconciliations kubernetes: convergence en continue vers l’état demandé
Quel que soit le provider, toutes les ressources managées Crossplane sont en dehors de tout namespace car leur “scope” et celui du cluster.
Les ressources Kubernetes additionnelles fournies par le provider AWS Crossplane
Cela suppose donc de gérer les noms des ressources Crossplane sur un même cluster pour éviter les conflits.
Pourquoi ce choix de design ? Parce que Crossplane introduit une séparation des rôles entre les “infra builder” qui sont considérés comme des administrateurs du cluster et les “app operator”. Les “infra builder” ont les droits sur l’ensemble du cluster, donc les ressources scopées “cluster” que sont les ressources managées Crossplane. A contrario, les “app operator” n’ont pas le droit d'interagir directement avec des ressources managées mais uniquement de travailler au sein de namespaces.
Dans le cas où un “app operator” doit déclarer des ressources managées Crossplane, c’est le mécanisme de composition qui est mis en œuvre.
Les compositions Crossplane traitent deux problématiques :
Les compositions reposent sur 3 types d’objets Kubernetes pris en charge par le contrôleur Crossplane :
Prenons l’exemple d’un réseau multicloud. Nous créons une XRD qui déclare une ressource composite “CompositeNetwork” et un claim dont le nom sera “Network” :
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: compositenetworks.aws.platformref.wescale.fr
spec:
# Comment claimNames to enforce static provisioning.
# Only creation of cluster scope resource
# "CompositeNetwork.aws.platformref.wescale.fr/v1alpha1" is possible
claimNames:
kind: Network
plural: networks
group: aws.platformref.wescale.fr
names:
kind: CompositeNetwork
plural: compositenetworks
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
…
En matière d’implémentation, nous créons une composition pour AWS. Le lien vers l’interface se fait via la clé compositeTypeRef. Notre composition est constituée de VPCs, tables de routages, subnets, NAT Gateway. Ces éléments sont des ressources managées déclarées dans spec.resources :
kind: Composition
metadata:
name: compositenetworks.aws.platformref.wescale.fr
labels:
provider: aws
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.platformref.wescale.fr/v1alpha1
kind: CompositeNetwork
resources:
- base:
apiVersion: ec2.aws.crossplane.io/v1beta1
kind: VPC
spec:
forProvider:
region: eu-west-3
cidrBlock: 192.168.0.0/16
enableDnsSupport: true
enableDnsHostNames: true
Enfin, un app operator qui aurait besoin de créer dynamiquement un réseau déclare un objet “Network” dans son namespace :
kind: Network
metadata:
name: demo-aws-network
namespace: ns-team-a
spec:
id: demo-aws-network
Les Compositions Crossplane, ou l’analogie avec les interfaces, les classes et les instances
Notre composition fait appel à plusieurs ressources managées, certaines ont besoin d’en référencer d’autres. Par exemple, une Internet Gateway AWS doit connaître l’identifiant du VPC. Pour cela, nous pouvons utiliser les Labels Selectors qui évaluent des labels gérés de manière uniforme sur tous les objets d’une composition. Crossplane fournit des mécanismes tels que les patches ou patchSet pour copier des valeurs depuis la ressource composite vers les ressources managées. L’inverse est possible : des éléments de ressources managées peuvent copier vers des attributs de la ressource composite.
resources:
- base:
apiVersion: ec2.aws.crossplane.io/v1beta1
kind: VPC
spec:
forProvider:
region: eu-west-3
cidrBlock: 192.168.0.0/16
enableDnsSupport: true
enableDnsHostNames: true
patches:
- fromFieldPath: spec.id
toFieldPath: metadata.labels[networks.aws.platformref.wescale.fr/network-id]
Ces valeurs injectées ou récupérées peuvent être associées à des chaînes de caractères formatées, des opérations mathématiques ou utiliser des maps.
Les ressources composites peuvent faire appel aux ressources managées de providers Crossplane, mais aussi à n’importe quelle ressource disponible sur le cluster Kubernetes : pod, deployment, CRD d’un autre opérateur ou encore une ressource composite tierce.
Par exemple, nous pouvons considérer une abstraction “cluster Kubernetes“ (simplement nommée “cluster”). Sur AWS, ce claim crée deux ressources composites :
Exemple de compositions imbriquées avec un “Cluster”
En jouant sur l’imbrication de ressources composites et les claims, nous pouvons traiter différents usages d’infrastructure :
Pour répondre à la question initiale : Crossplane, ça fonctionne plutôt bien. On peut gérer son infra et faire des abstractions pour en faciliter le maintien, tout en tirant profit de l’écosystème autour de Kubernetes. Par exemple, faciliter une approche GitOps via les outils Flux ou ArgoCD (à noter cette anomalie concernant la mauvaise gestion des ressources composites par ArgoCD), appliquer des règles de gouvernance via Gatekeeper ou Kyverno ou encore surveiller son Infrastructure as Code via des alertes Prometheus.
Cependant, pour adopter Crossplane, il y a deux “mais” :