La mutualisation de services sur plusieurs clusters avec Cilium et Microsoft AKS

thumbernail IA

Avec l'essor de la technologie cloud, les entreprises ont de plus en plus recours aux services mutualisés sur plusieurs clusters pour optimiser leurs performances et leur efficacité. C'est dans ce contexte que Cilium se démarque comme un outil innovant et puissant. Cet article a pour objectif de vous faire découvrir les avantages et l'utilisation de cette fonctionnalité, tout en détaillant son fonctionnement.

Le contexte technologique actuel

Le Cloud a révolutionné le monde de l'informatique en permettant l'accès et l'utilisation des données à partir de n'importe quel endroit. Cependant, les entreprises se retrouvent souvent confrontées à un défi de taille : comment gérer efficacement l'accès et l'utilisation des données dans différents clouds ? C'est là qu'intervient Cilium. Cilium est un logiciel open source qui permet d'automatiser l'accès et l'utilisation des données dans différents clouds (ou cluster situés dans différentes régions). En Associant à Microsoft AKS et Cilium CNI, les performances des services sont significativement améliorées, avec une réduction de 30% de la latence de routage des services par rapport à kube-proxy.

Les avantages de la mutualisation des services avec Cilium sur AKS

L'utilisation de Cilium sur AKS offre une série d'avantages. Pour commencer, il permet de mettre en œuvre des services mutualisés sur plusieurs clusters. Cela signifie que les entreprises peuvent partager leurs ressources et services entre différents clusters, réduisant ainsi les coûts et augmentant l'efficacité.

Quelques exemple d'utilisation du cluster-mesh de Cilium

  1. Déploiement d'applications multi-clusters : Avec ClusterMesh, vous pouvez déployer une application répartie sur plusieurs clusters Kubernetes dans différentes régions ou zones de cloud. Cette approche permet une meilleure disponibilité, une répartition de charge efficace et une tolérance aux pannes entre les clusters.

  2. Migration d'applications entre clusters : ClusterMesh facilite la migration d'applications entre clusters Kubernetes, que ce soit pour des mises à niveau, des tests ou des stratégies de déploiement complexes. Vous pouvez transférer des charges de travail d'un cluster à un autre de manière transparente et sans interruption de service.

  3. Partage de services entre clusters : Avec ClusterMesh, vous pouvez exposer et partager des services entre différents clusters Kubernetes. Par exemple, un service de base de données hébergé dans un cluster peut être accessible et utilisé par des applications déployées sur d'autres clusters, facilitant ainsi la réutilisation des ressources.

  4. Environnements multi-cloud et hybrides : ClusterMesh permet de connecter des clusters Kubernetes s'exécutant sur différents clouds publics (AWS, Azure, GCP) ou dans des environnements hybrides combinant des clouds publics et des infrastructures sur site. Cela offre une grande flexibilité pour les entreprises souhaitant bénéficier des avantages de différents fournisseurs de cloud ou d'une architecture multi-cloud.

  5. Isolement et segmentation de réseau : Avec la fonctionnalité de mise en réseau ClusterMesh, vous pouvez isoler et segmenter le trafic réseau entre les clusters. Cela permet de renforcer la sécurité et de contrôler les communications entre les différents clusters, en appliquant des règles de réseau et des politiques de sécurité appropriées.

  6. Mise à l'échelle globale : ClusterMesh facilite la mise à l'échelle globale d'applications en permettant de répartir les charges de travail sur plusieurs clusters Kubernetes dans différentes régions ou zones. Cela peut aider à réduire la latence pour les utilisateurs répartis géographiquement et à améliorer les performances globales de l'application.

En outre, Cilium assure une sécurité optimale

Voici quelques points importants concernant la sécurité offerte par Cilium :

  1. Politiques de réseau : Cilium applique les politiques de réseau au niveau du nœud. Cela signifie que les règles de sécurité sont mises en œuvre après que tous les nœuds ont été re-imaged ou mis à niveau. Cette approche garantit une cohérence dans l’application des politiques sur l’ensemble du cluster.

  2. "Re-imaging" des nœuds : Lorsqu’un nœud est "re-imaged" (par exemple, lors d’une mise à jour de l’image du nœud), Cilium rapplique automatiquement les règles de sécurité (network policy). Cela permet de s’assurer que les nœuds sont conformes aux politiques de sécurité définies.

  3. Mise à niveau de la version de Kubernetes : De manière similaire, lorsqu’une mise à niveau de la version de Kubernetes est effectuée, Cilium s’assure que les règles de sécurité sont toujours appliquées correctement, même après la mise à niveau.

  4. Visibilité et contrôle : Cilium offre une visibilité granulaire sur le trafic réseau et permet de définir des règles de sécurité basées sur des critères tels que les noms de service, les étiquettes, les ports, etc. En somme, Cilium contribue à renforcer la sécurité de votre cluster Kubernetes en appliquant des politiques de réseau de manière cohérente et en garantissant que les nœuds sont conformes aux règles de sécurité, même lors des mises à jour.

Pour établir une connexion sécurisée entre les clusters, il est nécessaire d'extraire l'Autorité de Certification (CA) utilisée par Cilium et de l'utiliser pour l'installation de Cilium sur l'autre cluster. Cela permet aux clusters de se faire mutuellement confiance en ce qui concerne le trafic crypté, ce qui est crucial pour un maillage de clusters sécurisé. Enfin, la mise en œuvre de services mutualisés sur plusieurs clusters nécessite la mise en place de l'affinité de service globale et la préparation du maillage de clusters de AKS à AKS dans notre exemple.

POC :

Pour ce Proof of Concept, une fois n'est pas coutume, je ne vais pas utiliser terraform pour le déploiement de l'infrastructure mais Pulumi Le repo est ici : https://github.com/herveleclerc/pulumi-aks-cilium Pré-requis :

  • Souscription Azure
  • Python > 3.11
  • Pulumi

J'utilise Pulumi comme outil d'IAC. Un de ses avantages est que l'on peut pleinement profiter des assistants IA pour le code, si besoin.

Création du projet pulumi :

mkdir pulumi-cilium-aks

cd pulumi-cilium-aks

pulumi new -g --language Python \
-n pulumi-cilium-aks \
-s pulumi-cilium-aks  \
-t -y

python3 -m venv venv

source venv/bin/activate

python -m pip install --upgrade pip setuptools wheel

python -m pip install -r requirements.txt

remplacez le contenu du fichier '''main.py''' par le code suivant

Fichier 'main'.py :

import pulumi
import pulumi_azure_native as azure_native
from   pulumi_azure_native import containerservice, resources, authorization
import base64

"""Creates a new Azure resource group.

Args:
    resource_group_name (str): The name of the resource group.
    location (str): The location of the resource group.

Returns:
    ResourceGroup: A new ResourceGroup object.
"""
def create_resource_group(resource_group_name, location):
    return azure_native.resources.ResourceGroup(resource_group_name,
                                                resource_group_name=resource_group_name,
                                                location=location)

"""Creates a new Azure virtual network.

Args:
    resource_group (ResourceGroup): The resource group to create the virtual network in.
    vnet_name (str): The name of the virtual network.
    address_prefixes (list[str]): The address prefixes (CIDR blocks) for the virtual network.

Returns:
    VirtualNetwork: The new VirtualNetwork object.
"""
def create_virtual_network(resource_group, vnet_name, address_prefixes):
    return azure_native.network.VirtualNetwork(vnet_name,
                                              resource_group_name=resource_group.name,
                                              virtual_network_name=vnet_name,
                                              location=resource_group.location,
                                              address_space=azure_native.network.AddressSpaceArgs(
                                                  address_prefixes=address_prefixes,
                                              ))

"""Creates a new Azure subnet.

Args:
    resource_group (ResourceGroup): The resource group to create the subnet in. 
    virtual_network (VirtualNetwork): The virtual network to create the subnet in.
    subnet_name (str): The name of the subnet.
    address_prefix (str): The address prefix (CIDR block) for the subnet.

Returns:
    Subnet: The new Subnet object.
"""
def create_subnet(resource_group, virtual_network, subnet_name, address_prefix):
    return azure_native.network.Subnet(subnet_name,
                                       address_prefix=address_prefix,
                                       resource_group_name=resource_group.name,
                                       virtual_network_name=virtual_network.name,
                                       subnet_name=subnet_name)

"""Creates a new virtual network peering between two virtual networks.

Args:
    resource_group1 (ResourceGroup): The resource group for the first virtual network.
    virtual_network1 (VirtualNetwork): The first virtual network to peer.
    resource_group2 (ResourceGroup): The resource group for the second virtual network. 
    virtual_network2 (VirtualNetwork): The second virtual network to peer.
    vnet_peering_name (str): The name for the virtual network peering.

Returns:
    VirtualNetworkPeering: The new VirtualNetworkPeering object.
"""
def create_vnet_peering(resource_group1, virtual_network1, resource_group2, virtual_network2, vnet_peering_name):
    return azure_native.network.VirtualNetworkPeering(vnet_peering_name,
                                                     resource_group_name=resource_group1.name,
                                                     virtual_network_name=virtual_network1.name,
                                                     virtual_network_peering_name=vnet_peering_name,
                                                     remote_virtual_network=azure_native.network.SubResourceArgs(id=virtual_network2.id,),
                                                     allow_virtual_network_access=True,
                                                     allow_forwarded_traffic=True,
                                                     opts=pulumi.ResourceOptions(depends_on=[virtual_network1, virtual_network2]),
                                                     )

def create_k8s_cluster(resource_group, k8s_cluster_name, subnet_node_id, service_cidr, dns_service_ip, pod_cidr):
    return azure_native.containerservice.ManagedCluster(
        k8s_cluster_name,
        resource_name_=k8s_cluster_name,
        resource_group_name=resource_group.name,
        location=resource_group.location,
        node_resource_group=f"{k8s_cluster_name}-vm",
        agent_pool_profiles=[{
            "count": 1,
            "max_pods": 250,
            "mode": "System",
            "name": "main",
            "node_labels": {"location": resource_group.location},
            "os_disk_size_gb": 30,
            "os_type": "Linux",
            "type": "VirtualMachineScaleSets",
            "vm_size": "Standard_B2s",
            "vnet_subnet_id": subnet_node_id
        }],
        dns_prefix='k8s-ne',
        enable_rbac=True,
        identity=azure_native.containerservice.ManagedClusterIdentityArgs(
            type="SystemAssigned",
        ),
        kubernetes_version='1.27.7',
        network_profile=azure_native.containerservice.ContainerServiceNetworkProfileArgs(
            network_plugin='none',
            pod_cidr=pod_cidr,
            service_cidr=service_cidr,
            dns_service_ip=dns_service_ip
        ),
    )

"""Creates Azure role assignments to grant the AKS managed identity access to the node resource group and cluster resource group.

Args:
  cluster_name (str): The name of the AKS cluster.
  resource_group_name (ResourceGroup): The resource group of the AKS cluster.
  cluster (ManagedCluster): The AKS managed cluster.

Returns:
  Tuple[RoleAssignment, RoleAssignment]: The two role assignments granting access.
"""
def create_role_assignments(cluster_name, resource_group_name, cluster):
    role_assignment_1 = authorization.RoleAssignment(
        f'role_assignment-aks-{cluster_name}',
        scope=f'/subscriptions/{config.subscription_id}/resourceGroups/{cluster_name}-vm',
        role_definition_id=f'/subscriptions/{config.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7'.format(
            subscriptionId=cluster.id.apply(lambda id: id.split('/')[2])),
        principal_id=cluster.identity.apply(lambda i: i.principal_id),
        principal_type='ServicePrincipal'
    )

    role_assignment_2 = authorization.RoleAssignment(
        f'role_assignment-aks-{cluster_name}-2',
        scope=resource_group_name.id,
        role_definition_id=f'/subscriptions/{config.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7'.format(
            subscriptionId=cluster.id.apply(lambda id: id.split('/')[2])),
        principal_id=cluster.identity.apply(lambda i: i.principal_id),
        principal_type='ServicePrincipal'
    )

    return role_assignment_1, role_assignment_2



"""Gets Kubernetes credentials for a managed AKS cluster.

Args:
  resource_group: The resource group of the AKS cluster.
  k8s_cluster: The AKS managed cluster.

Returns: 
  The Kubernetes config file contents.
"""
def get_k8s_credentials(resource_group, k8s_cluster):
    creds = containerservice.list_managed_cluster_user_credentials_output(
        resource_group_name=resource_group.name,
        resource_name=k8s_cluster.name)

    encoded = creds.kubeconfigs[0].value
    kubeconfig = encoded.apply(
        lambda enc: base64.b64decode(enc).decode())
    return kubeconfig

config = authorization.get_client_config()

"""
regions: A dictionary containing configuration details for Kubernetes clusters in different Azure regions.

Each region contains the following details:
- rg_name: Resource group name 
- location: Azure region location
- address_prefixes: Address prefixes for the VNet
- vnet_name: Name of the VNet
- subnet_node_name: Name of the node subnet 
- subnet_node_address_prefix: Address prefix for the node subnet
- subnet_pod_address_prefix: Address prefix for the pod subnet
- pod_cidr: CIDR range for pod IPs
- service_cidr: CIDR range for service IPs  
- dns_service_ip: IP address for DNS service
- k8s_cluster_name: Name of the Kubernetes cluster

This provides the configuration needed to deploy Kubernetes clusters in different regions with appropriate networking.
"""
regions = {
    "northeurope": {
        "rg_name": "rg-aks-northeurope",
        "location": "northeurope",
        "address_prefixes": ["10.0.0.0/20"],
        "vnet_name": "vnet-ne",
        "subnet_node_name": "node-subnet-ne",
        "subnet_node_address_prefix": "10.0.0.0/23",
        "subnet_pod_address_prefix": "10.0.4.0/22",
        "pod_cidr": "198.170.0.0/16",
        "service_cidr": "192.172.0.0/16",
        "dns_service_ip": "192.172.0.53",
        "k8s_cluster_name": "k8s-cluster-ne",
    },
    "westeurope": {
        "rg_name": "rg-aks-westeurope",
        "location": "westeurope",
        "address_prefixes": ["10.0.16.0/20"],
        "vnet_name": "vnet-we",
        "subnet_node_name": "node-subnet-we",
        "subnet_node_address_prefix": "10.0.16.0/23",
        "subnet_pod_address_prefix": "10.0.20.0/22",
        "pod_cidr": "198.174.0.0/16",
        "service_cidr": "192.176.0.0/16",
        "dns_service_ip": "192.176.0.53",
        "k8s_cluster_name": "k8s-cluster-we",
    },
    "francecentral": {
        "rg_name": "rg-aks-francecentral",
        "location": "francecentral",
        "address_prefixes": ["10.0.32.0/20"],
        "vnet_name": "vnet-fc",
        "subnet_node_name": "node-subnet-fc",
        "subnet_node_address_prefix": "10.0.32.0/23",
        "subnet_pod_address_prefix": "10.0.36.0/22",
        "pod_cidr": "198.178.0.0/16",
        "service_cidr": "192.180.0.0/16",
        "dns_service_ip": "192.180.0.53",
        "k8s_cluster_name": "k8s-cluster-fc",
    }
}

"""
vnet_peerings: Dictionary mapping region pairs to the peering names for connecting them.
    The keys are tuples of (region1, region2) and values are tuples 
    (peering1_name, peering2_name). This allows creating bidirectional 
    peerings between regions.
"""
vnet_peerings = {
    ("northeurope", "westeurope"): ("vnet-peering-ne-we", "vnet-peering-we-ne"),
    ("northeurope", "francecentral"): ("vnet-peering-ne-fc", "vnet-peering-fc-ne"),
    ("westeurope", "francecentral"): ("vnet-peering-we-fc", "vnet-peering-fc-we"),
}

"""
Create Azure resource groups, virtual networks, and subnets for each region.

The resource groups, virtual networks, and node subnets are created by looping through 
the regions dictionary and calling the respective Azure resource creation functions. The
names and properties for each resource are populated from the per-region dictionaries.
"""
resource_groups = {region: create_resource_group(regions[region]["rg_name"], regions[region]["location"]) for region in regions}
virtual_networks = {region: create_virtual_network(resource_groups[region], regions[region]["vnet_name"], regions[region]["address_prefixes"]) for region in regions}
subnet_nodes = {region: create_subnet(resource_groups[region], virtual_networks[region], regions[region]["subnet_node_name"], regions[region]["subnet_node_address_prefix"]) for region in regions}

"""
Create VNet peerings between regions. 

Loops through the vnet_peerings dictionary to create bidirectional VNet 
peerings between each region pair using the provided peering names.

Create Kubernetes clusters in each region.

Loops through the regions to create a Kubernetes cluster in each region's 
resource group, subnet, and other configured networking settings. 

Create role assignments to grant access to clusters.

Loops through regions to create role assignments granting access to the
cluster for the current user/service principal.

Get kubeconfig files for each cluster.

Loops through regions to download the kubeconfig files for accessing
each cluster.
"""
for (region1, region2), (peering_name1, peering_name2) in vnet_peerings.items():
    vnet_peering1 = create_vnet_peering(resource_groups[region1], virtual_networks[region1], resource_groups[region2], virtual_networks[region2], peering_name1)
    vnet_peering2 = create_vnet_peering(resource_groups[region2], virtual_networks[region2], resource_groups[region1], virtual_networks[region1], peering_name2)

k8s_clusters = {region: create_k8s_cluster(resource_groups[region], regions[region]["k8s_cluster_name"], subnet_nodes[region].id, regions[region]["service_cidr"], regions[region]["dns_service_ip"], regions[region]["pod_cidr"]) for region in regions}
role_assignments = {region: create_role_assignments(regions[region]["k8s_cluster_name"], resource_groups[region], k8s_clusters[region]) for region in regions}
kubeconfigs = {region: get_k8s_credentials(resource_groups[region], k8s_clusters[region]) for region in regions}

"""
Exports output values for use by other services:

- subnet_node_ids: Map of subnet ids for each region's node subnet 
- virtual_network_ids: Map of virtual network ids for each region
- vnet_peering_states: Map of peering states for each vnet peering
- kubeconfigs: Map of kubeconfig files for accessing each cluster

"""
pulumi.export('subnet_node_ids', {region: subnet_nodes[region].id for region in regions})
pulumi.export('virtual_network_ids', {region: virtual_networks[region].id for region in regions})
pulumi.export('vnet_peering_states', {f"{region1}_{region2}": (vnet_peering1.peering_state, vnet_peering2.peering_state) for (region1, region2), (peering_name1, peering_name2) in vnet_peerings.items()})
pulumi.export('kubeconfigs', kubeconfigs) 
  • Création des clusters kubernetes
pulumi up -y
  • Si tout se passe bien :
Type                                             Name                                  Status             
 +   pulumi:pulumi:Stack                              pulumi-cilium-aks-azure               created (334s)     
 +   ├─ azure-native:resources:ResourceGroup          rg-aks-northeurope                    created (1s)       
 +   ├─ azure-native:resources:ResourceGroup          rg-aks-westeurope                     created (1s)       
 +   ├─ azure-native:resources:ResourceGroup          rg-aks-francecentral                  created (1s)       
 +   ├─ azure-native:network:VirtualNetwork           vnet-fc                               created (5s)       
 +   ├─ azure-native:network:VirtualNetwork           vnet-we                               created (5s)       
 +   ├─ azure-native:network:VirtualNetwork           vnet-ne                               created (4s)       
 +   ├─ azure-native:network:Subnet                   node-subnet-ne                        created (5s)       
 +   ├─ azure-native:network:Subnet                   node-subnet-we                        created (5s)       
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-ne-we                    created (15s)      
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-we-ne                    created (26s)      
 +   ├─ azure-native:network:Subnet                   node-subnet-fc                        created (4s)       
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-fc-we                    created (12s)      
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-we-fc                    created (13s)      
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-ne-fc                    created (23s)      
 +   ├─ azure-native:network:VirtualNetworkPeering    vnet-peering-fc-ne                    created (12s)      
 +   ├─ azure-native:containerservice:ManagedCluster  k8s-cluster-ne                        created (257s)     
 +   ├─ azure-native:containerservice:ManagedCluster  k8s-cluster-fc                        created (247s)     
 +   ├─ azure-native:containerservice:ManagedCluster  k8s-cluster-we                        created (311s)     
 +   ├─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-fc-2  created (2s)       
 +   ├─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-fc    created (1s)       
 +   ├─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-ne-2  created (4s)       
 +   ├─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-ne    created (2s)       
 +   ├─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-we-2  created (4s)       
 +   └─ azure-native:authorization:RoleAssignment     role_assignment-aks-k8s-cluster-we    created (3s) 
  • Récupération des kubeconfig : Plus simple de le faire avec az cli que pulumi (pulumi stack output kubeconfigs --show-secrets)
az aks get-credentials -n k8s-cluster-we -g rg-aks-westeurope -f config
az aks get-credentials -n  k8s-cluster-ne -g rg-aks-northeurope -f config
az aks get-credentials -n  k8s-cluster-fc -g rg-aks-francecentral -f config
  • Positionnement sur le fichier de config et vérifications
export KUBECONFIG=./config

❯ kubectl get nodes --context k8s-cluster-fc
NAME                           STATUS     ROLES   AGE     VERSION
aks-main-96811076-vmss000000   NotReady   agent   8m23s   v1.27.7

❯ kubectl get nodes --context k8s-cluster-ne
NAME                           STATUS     ROLES   AGE     VERSION
aks-main-19291491-vmss000000   NotReady   agent   8m31s   v1.27.7

❯ kubectl get nodes --context k8s-cluster-we
NAME                           STATUS     ROLES   AGE     VERSION
aks-main-24498079-vmss000000   NotReady   agent   8m33s   v1.27.7

On remarque bien que la partie network n'est pas prête

  • Installation du CNI cilium

Je vais le faire avec la CLI de cilium on voit bien les différents paramètres a utiliser. Il est tout à fait possible de déployer Cilium avec un chart helm. Archives : https://github.com/cilium/charts Installation : https://docs.cilium.io/en/stable/installation/k8s-install-helm/

# k8s-cluster-ne 
cilium install \
--set azure.resourceGroup=rg-aks-northeurope \
--set cluster.id=1 \
--set ipam.operator.clusterPoolIPv4PodCIDRList='{198.170.0.0/16}' \
--context=k8s-cluster-ne

#k8s-cluster-we
cilium install \
--set azure.resourceGroup=rg-aks-westeurope \
--set cluster.id=2 --set ipam.operator.clusterPoolIPv4PodCIDRList='{198.174.0.0/16}' \
--context=k8s-cluster-we 

#k8s-cluster-fc 
cilium install \
--set azure.resourceGroup=rg-aks-francecentral \
--set cluster.id=3 --set ipam.operator.clusterPoolIPv4PodCIDRList='{198.178.0.0/16}' \
--context=k8s-cluster-fc 

Sortie :

Choses importantes :

  • les id différents pour chacun des clusters kubernetes
  • les variables clusterPoolIPv4PodCIDRList qui correspondent aux pod_cidr des clusters

Contrôle de l'installation de cilium :

for i in ne we fc 
do
  cilium status --context k8s-cluster-$i
  kubectl get nodes --context=k8s-cluster-$i
done
 /¯¯\
 /¯¯\__/¯¯\    Cilium:             OK
 \__/¯¯\__/    Operator:           OK
 /¯¯\__/¯¯\    Envoy DaemonSet:    disabled (using embedded mode)
 \__/¯¯\__/    Hubble Relay:       disabled
    \__/       ClusterMesh:        disabled

Deployment             cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
DaemonSet              cilium             Desired: 1, Ready: 1/1, Available: 1/1
Containers:            cilium             Running: 1
                       cilium-operator    Running: 1
Cluster Pods:          5/5 managed by Cilium
Helm chart version:    1.15.1
Image versions         cilium             quay.io/cilium/cilium:v1.15.1@sha256:351d6685dc6f6ffbcd5451043167cfa8842c6decf80d8c8e426a417c73fb56d4: 1
                       cilium-operator    quay.io/cilium/operator-generic:v1.15.1@sha256:819c7281f5a4f25ee1ce2ec4c76b6fbc69a660c68b7825e9580b1813833fa743: 1

#---

NAME                           STATUS   ROLES   AGE   VERSION
aks-main-16181278-vmss000000   Ready    agent   35m   v1.27.7

Cilium est bien installé et les noeuds sont prêts (Statut Ready)

Déploiement du clustermesh

Super Important : Avant le déploiement nous devons partager la même CA entre les clusters

Déploiement du clustermesh
Super Important :
Avant le déploiement nous devons partager la même CA entre les clusters

neat est un plugin kubectl très utile pour faire du reverse ingineering propre.

Toutes les informations inutiles sont supprimées du yaml. (https://github.com/itaysk/kubectl-neat)

# Application aux autres clusters

kubectl apply -f cilium-ca.yaml -n kube-system --context k8s-cluster-we

kubectl apply -f cilium-ca.yaml -n kube-system --context k8s-cluster-fc

Ne vous inquiétez pas des warnings car les secrets ont été créés de manière impérative.

  • Activation du clustermesh
for i in ne we fc 
do
  cilium clustermesh enable --context k8s-cluster-$i
done

Cette opération prend un peu de temps

Sortie :

  • Vérification de l'activation
 for i in ne we fc
do
  cilium clustermesh status --context k8s-cluster-$i
done

  • Connection des clusters les uns avec les autres
cilium clustermesh connect --context k8s-cluster-we --destination-context k8s-cluster-ne
cilium clustermesh connect --context k8s-cluster-we --destination-context k8s-cluster-fc
cilium clustermesh connect --context k8s-cluster-ne --destination-context k8s-cluster-fc

Ces opérations prennent un certain temps...

Vérifications

for i in ne we fc 
do
  cilium clustermesh status --context k8s-cluster-$i
done

Vous devriez avoir ca :

Test avec une appli

Je vais prendre l'exemple fourni par Cilium pour mettre en évidence un classique loadbalancing entre les 2 services sur des clusters différents

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.15.1/examples/kubernetes/clustermesh/global-service-example/cluster1.yaml \
--context k8s-cluster-ne

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.15.1/examples/kubernetes/clustermesh/global-service-example/cluster2.yaml \
--context k8s-cluster-we
 for i in {1..10}
do          
kubectl exec -ti deployment/x-wing --context k8s-cluster-ne -- curl rebel-base 
done 
{"Galaxy": "Alderaan", "Cluster": "Cluster-1"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-1"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-1"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"} 

On voit bien que les requêtes sont load balancées sur les 2 clusters

**Comment créer un service partagé **

Pour cet example on va juste supprimer le deployment de k8s-cluster-ne et ne garder que le service.

kubectl delete deploy rebel-base --context=k8s-cluster-ne
kubectl delete deploy x-wing --context=k8s-cluster-ne

kubectl get all

❯ kubectl get all
NAME                 TYPE        CLUSTER-IP        EXTERNAL-IP   
service/rebel-base   ClusterIP   192.172.248.186   <none>        80/TCP    108m

Maintenant lancez la commande

kubectl run -ti --rm curl --image curlimages/curl --context=k8s-cluster-ne -- sh

# au prompt
while :
do 
  curl rebel-base
done

Vous avez toujours accès aux pods qui sont déployés sur k8s-cluster-we via le service rebel-base :

{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}
{"Galaxy": "Alderaan", "Cluster": "Cluster-2"}

Si on regarde dans Hubble on a :

A cela s'ajoute la possibilité de faire du routage qui tient compte de la topologie avec deux modes :

Affinité locale de service : le trafic est d'abord routé vers les endpoints locaux avant d'aller vers d'autres clusters si nécessaire. ( annotation io.cilium/service-affinity: local)

apiVersion: v1
kind: Service
metadata:
  name: mysql-db
  annotations:
    io.cilium/global-service: "true"
    io.cilium/service-affinity: local
spec:
  type: ClusterIP
  ports:
  - port: 3306
  selector:
    name: mysql-db

Affinité distante de service : le trafic est préférentiellement routé vers les endpoints distants dans d'autres clusters. (annotation io.cilium/service-affinity: remote)

apiVersion: v1
kind: Service
metadata:
  name: vault
  annotations:
    io.cilium/global-service: "true"
    io.cilium/service-affinity: remote
spec:
  type: ClusterIP
  ports:
  - port: 8200
  selector:
    name: vault

Avec cilium et clustermesh on peut donc créer des architectures logicielles permettant d'avoir au niveau des applications : H - aute disponibilité et tolérance aux pannes - Découverte transparente des services - Routage IP des pods sans effort (rien à faire à part poser les bonnes annotations) - Services partagés entre les clusters - Application uniforme des politiques de réseau du niveau 3 jusqu'au niveau 7. (clustermesh + servicemesh)


Conclusions

Cilium est plus qu'un CNI basique il ajoute un ensemble de fonctionnalités réseaux très intéressantes :

  • ClusterMesh
  • NetworkPolicy permettant de faire des règles bien plus évoluées que les network policy classiques (ex: egress toFQDNs)
  • Service Mesh : intégration du service mesh directement au niveau kernel en utilisant eBPF. Donc des performances et une simplicité à toutes épreuves.
  • Observabilité : Notamment avec Hubble

Découvrez les technologies d'alter way