Capsule sur AKS

thumbernail kubernetes

1. Avant Propos

Premier article sur d'une série portant sur les sujets de "multi tenants".

Kubernetes est une plateforme puissante, souple et évolutive d'orchestration de conteneurs qui peut être utilisée pour déployer et gérer des applications à grande échelle. Cependant, Kubernetes peut être coûteux et difficile à gérer, en particulier lorsque plusieurs équipes ou organisations l'utilisent en même temps.

Une des solution est d'utiliser Capsule

Capsule est un framework multi-tenant basé sur Kubernetes qui facilite la gestion de clusters Kubernetes partagés.

2. Table des matières

3. Avantages de Capsule

  • Le Multi-Tenancy Capsule permet de mettre en place une architecture multi-tenant dans un cluster Kubernetes. On peut ainsi créer des "partitions" logiques regroupant un ensemble d'utilisateurs, de namespaces, de droits associés et quota. Très pratique pour faire coexister au sein d'un même cluster des populations ou environnement différents. Cela évite ainsi de provisionner des clusters dédiés.

  • Les "Policy" Elles sont basées sur des règles pour gérer les ressources, les accès, les quotas, limites etc sur les clusters.

  • Le mode déclaratif Parfait pour gérer l'ensemble via du GitOps.

  • La légereté : L'architecture en mode microservices minimalistes.

  • Un projet CNCF Il est au niveau SandBox : https://www.cncf.io/projects/capsule/

4. Comment ca marche ?

  • Un contrôleur (Capsule Controler) va gérer une Custom Resource de type Tenant.

Dans chaque tenant, les utilisateurs rattachés pourront créer leurs propres namespace et pourront partager toutes les resources assignées.

  • Un moteur règles (Capsule Policy Engine) va se charger de l'isolation des tenants les uns des autres. Les Network et Security Policies, Resource Quota, Limit Ranges, RBAC, et autres other policies définies au niveau Tenant sont héritées par tous les namespaces du tenant.

Un fois déployé le contrôleur vous pourrez faire un kubectl explain tenant.spec pour connaitre l'ensemble des paramètres que vous pourrez mettre à un tenant.

On a ainsi un mode de fonctionnement qui permet d'avoir des utilisateurs qui peuvent en toute autonomie gérer leur espace sans intervention d'un administrateur du cluster.

5. Utilisation avec un service managé Azure Kubernetes Service (AKS)

Nous allons utiliser un service managé AKS avec l'intégration d'Azure Active Directory (Microsoft Entra ID) pour notre POC.


6. POC

6.1. Création d'un cluster AKS avec l'intégration AAD.

6.1.1. Création de différents groupe dans Entra (Préparation)

Nous allons utiliser la CLI d'azure pour aller au plus simple (tout peut être fait avec opentofu )

6.1.1.1. Création du groupe Capsule Admin

CAPSULE_ADMIN_GROUP_ID=$(az ad group create \
  --display-name capsuleAdminGroup \
  --mail-nickname capsuleAdminGroup \
  --query id \
  --output tsv)

# Check 
echo $CAPSULE_ADMIN_GROUP_ID
03c9ccac-xxx-xxx-xx-xxxxx

6.1.1.2. Création du user qui sera "cluster admin"

💡Note : Il faudra bien sûr adapter le domaine avec vos données (ici @microsoftalterway.onmicrosoft.com) :

CAPSULE_ADMIN_USER_NAME="capsule-admin@microsoftalterway.onmicrosoft.com"
CAPSULE_ADMIN_USER_PASSWORD="@#Temporary:Password#@"

CAPSULE_ADMIN_USER_ID=$(az ad user create \
  --display-name ${CAPSULE_ADMIN_USER_NAME} \
  --user-principal-name ${CAPSULE_ADMIN_USER_NAME} \
  --password ${CAPSULE_ADMIN_USER_PASSWORD} \
  --query id -o tsv)

az ad group member add \
  --group capsuleAdminGroup \
  --member-id $CAPSULE_ADMIN_USER_ID

6.1.2. Création du groupe capsuleDevGroup

CAPSULE_DEV_GROUP_ID=$(az ad group create \
  --display-name capsuleDevGroup \
  --mail-nickname capsuleDevGroup \
  --query id \
  --output tsv)

6.1.3. Création d'un user dans le groupe capsuleDevGroup

Cet utilisateur sera considéré comme le propriétaire du tenant DEV

CAPSULE_DEV_USER_NAME="capsule-user-dev@microsoftalterway.onmicrosoft.com"
CAPSULE_DEV_USER_PASSWORD="@#Temporary:Password#@"

CAPSULE_DEV_USER_ID=$(az ad user create \
  --display-name ${CAPSULE_DEV_USER_NAME} \
  --user-principal-name ${CAPSULE_DEV_USER_NAME} \
  --password ${CAPSULE_DEV_USER_PASSWORD} \
  --query id -o tsv)

az ad group member add \
  --group capsuleDevGroup \
  --member-id $CAPSULE_DEV_USER_ID

6.1.4. Création du groupe capsuleStagingGroup

CAPSULE_STAGING_GROUP_ID=$(az ad group create \
  --display-name capsuleStagingGroup \
  --mail-nickname capsuleStagingGroup \
  --query id \
  --output tsv)

6.1.5. Création d'un user dans le groupe capsuleStagingGroup

Cet utilisateur sera considéré comme le propriétaire du tenant STAGING

CAPSULE_STAGING_USER_NAME="capsule-user-staging@microsoftalterway.onmicrosoft.com"
CAPSULE_STAGING_USER_PASSWORD="@#Temporary:Password#@"

CAPSULE_STAGING_USER_ID=$(az ad user create \
  --display-name ${CAPSULE_STAGING_USER_NAME} \
  --user-principal-name ${CAPSULE_STAGING_USER_NAME} \
  --password ${CAPSULE_STAGING_USER_PASSWORD} \
  --query id -o tsv)

az ad group member add \
  --group capsuleStagingGroup \
  --member-id $CAPSULE_STAGING_USER_ID

6.1.6. Création du groupe capsuleGroup

💡 Note : Tous les utilisateurs et groupes étant propriétaires de tenants doivent être dans ce groupe !

CAPSULE_GROUP_ID=$(az ad group create \
  --display-name capsuleGroup \
  --mail-nickname capsuleGroup \
  --query id \
  --output tsv)

# Dev Group Assignation
az ad group member add \
  --group capsuleGroup \
  --member-id $CAPSULE_DEV_USER_ID

# Staging Group Assignation
az ad group member add \
  --group capsuleGroup \
  --member-id $CAPSULE_STAGING_USER_ID

6.1.6.1. Users

6.1.6.2. Groupes

6.1.6.3. Memberships


6.1.7. Création d'un cluster AKS

Nous allons créer un Cluster AKS avec l'intégration d'AAD, le RBAC activé, et une API publique

Ici nous ne cherchons pas à faire un cluster AKS dans les règles de l'art, nous laissons Azure mettre les valeurs pour un grand nombre de resources (vnet, subnet, taille vm ...)

Nous allons dire au moment de la création quels sont les groupes d'utilisateurs ayant les privilèges de `cluster admin``.

💡 Note : Il faudra bien sûr adapter le domaine avec vos données (ici @microsoftalterway.onmicrosoft.com) :

❗️ Rappelez vous de l'ID ($CAPSULE_ADMIN_GROUP_ID)

# resource-group
az group create --name aw-capsule --location francecentral


# aks cluster
az aks create \
  --resource-group aw-capsule \
  --node-resource-group aw-capsule-vm \
  --name aw-capsule \
  --enable-aad \
  --enable-azure-rbac \
  --aad-admin-group-object-ids $CAPSULE_ADMIN_GROUP_ID \
  --network-plugin azure \
  --network-policy calico

💡 Note : il faut installer kubelogin pour pouvoir vous authentifier sur ce cluster.

https://aka.ms/aks/kubelogin

6.1.8. Récupération du kubeconfig permettant l'administration du cluster kubernetes

# 1: Si vous souhaitez le mettre dans votre fichier ~/.kube/config
az aks get-credentials --resource-group aw-capsule --name aw-capsule 

# 2: Si vous souhaitez le mettre dans un fichier séparé (il faudra utiliser le flag --kubeconfig-file ou la variable KUBEFONFIG pour pointer sur le cluster)

az aks get-credentials --resource-group aw-capsule --name aw-capsule --file ~/.kube/aw-capsule-config

Je vais pour le POC utiliser la deuxième solution (2:)

export KUBECONFIG=~/.kube/aw-capsule-config

# kubectl get nodes

To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXX to authenticate.
  • Utiliser le compte capsule-admin@microsoftalterway.onmicrosoft.com et son mot de passe (@#Temporary:Password#@)

vous devriez avoir dans la fenêtre de votre navigateur :

Azure Kubernetes Service AAD Client
Vous vous êtes connecté à l'application Azure Kubernetes Service AAD Client sur votre appareil. Vous pouvez maintenant fermer cette fenêtre.

et la commande devrait fonctionner 😀

 kubectl get nodes
NAME                                STATUS   ROLES   AGE   VERSION
aks-nodepool1-12585846-vmss000000   Ready    agent   6m   v1.26.6
aks-nodepool1-12585846-vmss000001   Ready    agent   6m   v1.26.6
aks-nodepool1-12585846-vmss000002   Ready    agent   6m   v1.26.6

6.2. Ajout des rôles au niveau de IAM de la souscription Azure

Il faut ajouter pour les différents groupes (dev et staging) les droits suivants

"Reader" et "Azure Kubernetes Service Cluster User Role" afin de pouvoir télécharger les kubeconfig avec la commande az aks get-credential...


6.3. Installation de Capsule

6.3.1. Utiliser le kubeconfig de l'admin du cluster

❗️Pour rappel :

az aks get-credentials --resource-group aw-capsule --name aw-capsule --file ~/.kube/aw-capsule-config
export KUBECONFIG=~/.kube/aw-capsule-config

6.3.2. Installation du chart helm de l'operateur Capsule

6.3.2.1. Référence du repo

helm repo add clastix https://clastix.github.io/charts

# Si déja installé
helm repo update

6.3.2.2. Déploiement du chart

Il vous faudra l'id du groupe capsuleGroup.

# Pour Rappel
CAPSULE_GROUP_ID=$(az ad group create \
  --display-name capsuleGroup \
  --mail-nickname capsuleGroup \
  --query id \
  --output tsv)

Capsule doit connaître les groupes autorisés avec lesquels il travaillera.

Nous devons enregistrer l'ID d'objet du groupe Azure AD capsuleGroup en tant que groupe d'utilisateurs Capsule sous CapsuleConfiguration

helm upgrade --install capsule clastix/capsule \
   -n capsule-system \
   --create-namespace \
   --set manager.options.forceTenantPrefix=true \
   --set "manager.options.capsuleUserGroups[0]=$CAPSULE_GROUP_ID"

👀 Contrôle :

 kubectl get deploy -n capsule-system
NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
capsule-controller-manager   1/1     1            1           78s

6.3.3. Installation du chart Helm Capsule Proxy

Capsule Proxy est un module complémentaire pour Capsule Operator qui résout certains problèmes de RBAC lors de l'activation de la multi-location dans Kubernetes, car les utilisateurs ne peuvent pas lister les ressources au niveau cluster.

Kubernetes RBAC ne peut pas répertorier uniquement les ressources détenues à l'échelle du cluster, car il n'existe aucune API filtrée par ACL. Par exemple:

  • kubectl get namespaces échoue même si l'utilisateur a les droits.

Comment fonctionne Capsule Proxy

                +-----------+          +-----------+         +-----------+
 kubectl ------>|:443       |--------->|:9001      |-------->|:6443      |
                +-----------+          +-----------+         +-----------+
                ingress-controller     capsule-proxy         kube-apiserver
                load-balancer
                nodeport
                HostPort...

On peut exposer capsule-proxy de différentes manières :

  • Ingress
  • NodePort Service
  • LoadBalance Service
  • HostPort
  • HostNetwork

Dans notre cas nous allons utiliser un loadbalancer. Sur azure nous aurons une ip publique. Nous allons aussi utiliser une fonctionnalité qui nous permet grace a une annotations de créer un fqdn du type [name].francecentral.cloudapp.azure.com qui nous permettra d'acceder directement au service capsule-proxy par une url distincte.

❗️Pour rappel :

CAPSULE_ADMIN_GROUP_ID=$(az ad group create \
  --display-name capsuleAdminGroup \
  --mail-nickname capsuleAdminGroup \
  --query id \
  --output tsv)
helm upgrade --install capsule-proxy clastix/capsule-proxy \
   -n capsule-system \
   --set service.type=LoadBalancer \
   --set service.port=443 \
   --set options.oidcUsernameClaim=unique_name \
   --set "options.ignoredUserGroups[0]=$CAPSULE_ADMIN_GROUP_ID" \
   --set "options.additionalSANs[0]=capsule-proxy.francecentral.cloudapp.azure.com" \
   --set service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name"=capsule-proxy

💡 Note : Vous devrez adapter le SAN : capsule-proxy.francecentral.cloudapp.azure.com et l'annotation

👀 Contrôle :

 kubectl get po,secrets,svc -n capsule-system
NAME                                             READY   STATUS    RESTARTS   AGE
pod/capsule-controller-manager-c98c8fb88-7xzhm   1/1     Running   0          4m11s
pod/capsule-proxy-945bc469d-lc9jz                1/1     Running   0          2m

NAME                                         TYPE                 DATA   AGE
secret/capsule-proxy                         Opaque               3      116s
secret/capsule-tls                           Opaque               3      4m11s
secret/sh.helm.release.v1.capsule-proxy.v1   helm.sh/release.v1   1      2m
secret/sh.helm.release.v1.capsule.v1         helm.sh/release.v1   1      4m11s

NAME                                                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
service/capsule-controller-manager-metrics-service   ClusterIP      10.0.213.12    <none>        8080/TCP        4m11s
service/capsule-proxy                                LoadBalancer   10.0.2.95      20.199.4.73   443:30350/TCP   2m
service/capsule-proxy-metrics-service                ClusterIP      10.0.163.163   <none>        8080/TCP        2m
service/capsule-webhook-service                      ClusterIP      10.0.9.76      <none>        443/TCP         4m11s


curl -k https://capsule-proxy.francecentral.cloudapp.azure.com
{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"cannot retrieve user and group: unauthenticated users not supported","reason":"Forbidden","code":403}%

6.4. Création des Tenants

6.4.1. DEV

CAPSULE_DEV_USER_ID=$(az ad user list --upn capsule-user-dev@microsoftalterway.onmicrosoft.com  -o tsv --query "[0].id")

echo $CAPSULE_DEV_USER_ID

kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
kind: Tenant
metadata:
  name: dev
spec:
  owners:
  - name: ${CAPSULE_DEV_GROUP_ID}
    kind: Group
EOF tenant.capsule.clastix.io/dev created

6.4.2. Staging

CAPSULE_STAGING_USER_ID=$(az ad user list --upn capsule-user-staging@microsoftalterway.onmicrosoft.com  -o tsv --query "[0].id")

echo $CAPSULE_STAGING_USER_ID

kubectl apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
kind: Tenant
metadata:
  name: staging
spec:
  owners:
  - name: ${CAPSULE_STAGING_GROUP_ID}
    kind: Group
EOF tenant.capsule.clastix.io/staging created

👀 Contrôle :

kubectl get tenants.capsule.clastix.io 

NAME      STATE    NAMESPACE QUOTA   NAMESPACE COUNT   NODE SELECTOR   AGE
dev       Active                     0                                 1m39s
staging   Active                     0                                 5s

6.5. Utilisation des Tenants

Nous allons maintenant accéder au cluster avec l'utilisateur capsule-user-dev qui est attaché au tenant dev via le groupe capsuleDevGroup

Je vous conseille de faire la manipulation suivante avant de commencer, pour être sur le bon utilisateur si vous utiliser le même compte utilisateur sur votre machine. Vous n'avez pas à le faire si vous vous connectez avec un autre utilisateur sur votre machine locale.

az logout
kubelogin remove-tokens

6.5.1. Récupération du kubeconfig pour les utilisateurs dev

az login
az aks get-credentials --resource-group aw-capsule --name aw-capsule --file ~/.kube/dev-capsule-config

✅ Vous allez devoir vous connecter avec le user capsule-user-dev

export KUBECONFIG=~/.kube/dev-capsule-config

👀 Contrôle :

 az logout kubelogin remove-tokens
❯ az login
A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`.
CloudName    HomeTenantId                          IsDefault    Name                     State    TenantId
-----------  ------------------------------------  -----------  -----------------------  -------  ------------------------------------
AzureCloud   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  True         Conso Interne Alter Way  Enabled  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx

❯ az aks get-credentials --resource-group aw-capsule --name aw-capsule --file ~/.kube/dev-capsule-config
Merged "aw-capsule" as current context in /Users/hleclerc/.kube/dev-capsule-config

6.5.2. Utilisation de l'utilisateur capsule-user-dev

Si vous tapez une commande kubectl ..., vous allez devoir vous identifier pour pouvoir utiliser le cluster kubernetes via l'api publique de celui-ci.

 export KUBECONFIG=~/.kube/dev-capsule-config
❯ kubectl get po
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code G7RCDSJW8 to authenticate.

Essayez une commande toute simple

 kubectl get po
Error from server (Forbidden): pods is forbidden: User "capsule-user-dev@microsoftalterway.onmicrosoft.com" cannot list resource "pods" in API group "" in the namespace "default": User does not have access to the resource in Azure. Update role assignment to allow access.

Cette erreur est normale car dans le tenant dev vous n'avez pas accès au namespace default car celui-ci n'appartient pas au tenant.

par contre si vous faîtes la commande :

 kubectl create ns dev-demo
namespace/dev-demo created

Il n'y a pas d'erreur car vous êtes dans le tenant dev vous êtes le propriétaire de ce tanant et vous venez de créer un namespace dev-demo

💡 Note : Capsule vous force à préfixer tous les noms de namespace par dev-

Si vous tapez la commande :

 kubectl get ns
Error from server (Forbidden): namespaces is forbidden: User "capsule-user-dev@microsoftalterway.onmicrosoft.com" cannot list resource "namespaces" in API group "" at the cluster scope: User does not have access to the resource in Azure. Update role assignment to allow access.

C'est pour cela que nous allons utiliser le capsule-proxy pour pouvoir lister les objets qui n'ont pas de namespace.

Nous allons manipuler le fichier kubeconfig pour changer le fqdn du serveur et pointer sur le proxy : aw-capsule-proxy.francecentral.cloudapp.azure.com par exemple https://capsule-proxy.caas.fr:443

# Récupérer le nom du cluster kubectl config get-clusters

# Modification de l'url de l'api  kubectl config set-cluster aw-capsule  --server=https://capsule-proxy.caas.fr:443

# Recupérer le nom du user  kubectl config get-users

# Modification du user si besoin dans le context (clusterUser_aw-capsule_aw-capsule)
kubectl config set-context aw-capsule --cluster=aw-capsule --user=clusterUser_aw-capsule_aw-capsule

Si vous tapez la commande par exemple

 kubectl get ns 

Vous allez certainement avoir une erreur Unable to connect to the server: tls: failed to verify certificate: x509: certificate signed by unknown authority.

Pour éviter ca utilisez le flag --insecure-skip-tls-verify.

 kubectl get ns --insecure-skip-tls-verify
I0928 09:10:45.534157    3741 versioner.go:58] Get https://capsule-proxy.francecentral.cloudapp.azure.com:443/version?timeout=5s: x509: certificate signed by unknown authority


NAME       STATUS   AGE
dev-demo   Active   17m

Et voila 😀😏💪 !

Bravo ! Vous avez votre premier namespace dans le tenant dev.

Vous ne voyez que les composants qui appartiennent à votre tenant.

💡 Note :

J'ai utilisé certbot pour rapidement générer les certificats pour le fqdn capsule-proxy.caas.fr.

certbot -d capsule-proxy.caas.fr --manual --preferred-challenges dns certonly

Si vous ne voulez pas avoir l'erreur X509 il vous faudra remplacer certificate-authority-data par la valeur du tls.cert que vous avez utilisé (en base64)

Moi j'ai pris la fullchain.pem que certbot m'a généré

ensuite il faut mettre à jour

  • Le secret de capsule-proxy dans le ns capsule-system
 kubectl delete secret capsule-proxy
❯ kubectl -n capsule-system create secret tls capsule-proxy --cert=./tls.crt --key=./tls.key
  • capsule proxy :
helm upgrade --install capsule-proxy clastix/capsule-proxy \
   -n capsule-system \
   --set service.type=LoadBalancer \
   --set service.port=443 \
   --set options.oidcUsernameClaim=unique_name \
   --set "options.ignoredUserGroups[0]=$CAPSULE_ADMIN_GROUP_ID" \
   --set "options.additionalSANs[0]=capsule-proxy.caas.fr" \
   --set service.annotations."service\.beta\.kubernetes\.io/azure-dns-label-name"=capsule-proxy \
   --set options.generateCertificates=false

CA devrait être bon plus d'erreur de certicat


Gestion des règles au niveau d'un tenant

Network policy

Si on défini des network policy au niveau du tenant elles seront propagées à tous les namespaces du tenant.

un exemple :

apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
  name: dev
spec:
  ingressOptions:
    hostnameCollisionScope: Disabled
  networkPolicies:
    items:
    - egress:
      - to:
        - ipBlock:
            cidr: 0.0.0.0/0
      ingress:
      - from:
        - namespaceSelector:
            matchLabels:
              capsule.clastix.io/tenant: dev
      podSelector:    
        matchLabels:
      policyTypes:
      - Ingress
      - Egress
  owners:
  - clusterRoles:
    - admin
    - capsule-namespace-deleter
    kind: Group
    name: 0fd5e5bb-39bb-468c-b343-dcdfb01e92d0
  resourceQuotas:
    scope: Tenant

Si on applique les modifications au tenant dev alors

  • Seuls les pods des namespaces du tenant pourrons discuter entre et consommer d'autre "services" interne ou externe au cluster
  • Aucun pod ne pourras accéder au pods du tenant dev

On voit bien l'ajout des network policy automatiquement par le contrôleur capsule.

 kubectl get netpol -A
NAMESPACE     NAME                 POD-SELECTOR             AGE
dev-app       capsule-dev-0        <none>                7m59s
dev-demo      capsule-dev-0        <none>                7m45s
kube-system   konnectivity-agent   app=konnectivity-agent   8h

Vous pouvez voir ici une vidéo de démonstration sur l'exemple des network policy


Références

  • Site Web de Capsule: https://clastix.io/capsule/
  • Documentation de Capsule: https://capsule.clastix.io/docs/
  • Dépôt GitHub de Capsule: https://github.com/clastix/capsule

Découvrez les technologies d'alter way