Déployer Cilium et AKS avec Terraform (Partie 2)

Joseph Ligier
10 min readDec 8, 2023

--

Introduction

Dans la première partie, nous avons vu toutes les possibilités du code via le fichier variables.tf pour déployer Cilium et AKS. Dans cette deuxième partie, nous allons voir les ressources Terraform que j’ai utilisé pour déployer Cilium et AKS. Puis nous regarderons ce qui est installé avec Cilium géré par Azure. Rappelons le code Terraform (la branche blog) :

Je rappelle que dans le monde du cloud, je connais surtout AWS. Donc c’est possible que je puisse dire des approximations sur Azure. N’hésitez pas à me le dire, je corrigerai dès que possible.

Ressources pour le réseau

Avant de déployer AKS, il faut mettre en place la pile réseau. Voici les ressources réseaux :

resource "azurerm_virtual_network" "this" {
address_space = var.vnet.address_space
name = var.vnet.name
resource_group_name = var.resource_group_name
location = var.location
}

resource "azurerm_subnet" "node" {
address_prefixes = var.subnet_node.address_prefixes
name = var.subnet_node.name
virtual_network_name = azurerm_virtual_network.this.name
resource_group_name = var.resource_group_name
}

resource "azurerm_subnet" "pod" {
count = var.cilium.type == "cilium_azure" ? 1 : 0
address_prefixes = var.subnet_pod.address_prefixes
name = var.subnet_pod.name
virtual_network_name = azurerm_virtual_network.this.name
resource_group_name = var.resource_group_name

delegation {
name = "aks-delegation"

service_delegation {
actions = [
"Microsoft.Network/virtualNetworks/subnets/join/action",
]
name = "Microsoft.ContainerService/managedClusters"
}
}
}
  • azurerm_virtual_network : c’est la ressource qui définit le vnet (le vpc dans le monde AWS). On doit définir son nom, son cidr et les paramètres propres à Azure : ressource groupe et sa géolocalisation.
  • azurerm_subnet (node) : c’est la ressource qui définit un subnet du vnet pour les nœuds kubernetes. On doit définir son nom, son cidr, son vnet et son ressource groupe.
  • azurerm_subnet (pod) : c’est la ressource qui définit un subnet du vnet pour les pods kubernetes. Il est uniquement nécessaire pour la version cilium gérée par Azure. Le count sert à vérifier cela. Les autres paramètres sont similaires. J’ai rajouté ensuite delegation. Si on ne le met pas, quand on fait une seconde fois terraform apply, il veut supprimer cela. Voilà pourquoi je l’ai rajouté.

On remarque les variables (ressource_group_name et location) sont “incrustés” au code. Il n’est pas possible de les mettre dans une variable globale comme il est possible sur AWS pour le numéro de compte et la région. Il y a aussi des trucs qui se rajoutent sans explication (comme delegation). Par contre les ressources réseaux sont plus simples que sur AWS, je n’ai pas défini de tables de routages ou d’internet gateway. C’est pour cette raison que je n’ai pas fait ou utilisé de module Terraform. En production, il faut certainement en utiliser pour avoir une architecture plus sécurisée.

Ressource AKS

Voici la ressource AKS :

resource "azurerm_kubernetes_cluster" "this" {
name = var.aks.name
kubernetes_version = var.aks.version

azure_policy_enabled = true

dns_prefix = var.aks.dns_prefix

default_node_pool {
name = var.aks.default_node_pool.name
node_count = var.aks.default_node_pool.node_count
vm_size = var.aks.default_node_pool.vm_size
vnet_subnet_id = azurerm_subnet.node.id
pod_subnet_id = var.cilium.type == "cilium_azure" ? azurerm_subnet.pod[0].id : null
}

network_profile {
network_plugin = local.network[var.cilium.type].network_plugin
network_policy = local.network[var.cilium.type].network_policy
ebpf_data_plane = local.network[var.cilium.type].ebpf_data_plane
}

identity {
type = "SystemAssigned"
}

location = var.location
resource_group_name = var.resource_group_name
}
  • name : le nom du cluster kubernetes
  • kubernetes_version : la version du cluster kubernetes. J’avais défini 1.27. Ça fonctionnait mais après avoir refait un plan il voulait modifier le cluster en 1.27 car il était en 1.27.3. Donc j’ai finalement mis 1.27.3. Quand vous lirez ces lignes, il faudra certainement modifier cette valeur.
  • azure_policy_enabled : c’est également une valeur qui s’est ajouté après avoir déployé. Donc je l’ai rajouté sans savoir ce que c’est.
  • dns_prefix : c’est obligatoire, c’est pour définir une url pour l’api de kubernetes. C’est dommage que ça soit obligatoire, je préférerais laisser Azure s’en charger.
  • default_node_pool: c’est là où on définit les nœuds kubernetes : son nombre, sa capacité et son réseau (avec notamment l’exception en cas cilium géré par Azure).
  • network_profile : c’est là où on décide si on utilise cilium géré par Azure ou byocni (Bring your own cni, débrouille toi avec ton propre cni) pour notre cas.
  • identity : ce n’est pas “important”. je ne crois pas que c’est indispensable.
  • location et resource_group_name : on l’a déjà vu

Même remarque que pour les ressources réseaux : c’est plus simple que sur AWS. Mais il y a des trucs qui apparaissent après coups. Ça arrive sous AWS mais en général c’est pour une nouvelle fonctionnalité 6 mois après.

On peut rajouter également la ressource azurerm_kubernetes_cluster_node_pool pour plus de souplesse.

kubeconfig

Le kubeconfig est le fichier qui permet de communiquer avec l’API de kubernetes (par exemple avec kubectl). Pour sa génération :

resource "local_file" "this" {
content = azurerm_kubernetes_cluster.this.kube_config_raw
filename = "${path.module}/kubeconfig"
}

Rien d’extraordinaire. Je trouve juste dommage qu’il ait besoin d’utiliser un provider Terraform pour générer un bête fichier.

Cilium

Si vous choisissez cilium_custom, cette partie est pour vous :

resource "terraform_data" "kube_proxy_disable" {
count = var.cilium.type == "cilium_custom" && var.cilium.kube-proxy-replacement ? 1 : 0
provisioner "local-exec" {
command = "kubectl -n kube-system patch daemonset kube-proxy -p '\"spec\": {\"template\": {\"spec\": {\"nodeSelector\": {\"non-existing\": \"true\"}}}}'"
environment = {
KUBECONFIG = "./kubeconfig"
}
}

depends_on = [
local_file.this
]
}

module "cilium" {
count = var.cilium.type == "cilium_custom" ? 1 : 0
source = "littlejo/cilium/helm"
version = "0.4.1"
ebpf_hostrouting = var.cilium.ebpf-hostrouting
hubble = var.cilium.hubble
hubble_ui = var.cilium.hubble-ui
azure_resource_group = var.resource_group_name
kubeproxy_replace_host = local.kubeproxy_replace_host
depends_on = [
terraform_data.kube_proxy_disable,
]
}

La resource terraform_data va lancer une commande kubectl pour désactiver kube-proxy le cas échéant. Je ne vais pas supprimer complètement le DaemonSet kube-proxy mais lui dire de s’exécuter uniquement dans certains nœuds qui ont un label non-existing à true. Si je supprimais complètement kube-proxy avec kubectl, il est recréé plus tard par Azure. C’est un peu perturbant. Cette partie du code n’est pas très “propre”. En effet, cela nécessite d’avoir installé kubectl sur la machine qui lance terraform. Par exemple, il est probable que cela ne fonctionne pas sur Terraform Cloud.

Le module cilium est un module helm qui permet d’installer Cilium. J’aurais pu ne pas faire de module helm. Mais ça permet de simplifier les différent type d’installation. On verra plus en détails dans la prochaine partie ce module.

On va maintenant voir le mélange

Nous allons maintenant voir le déploiement de AKS avec la version Cilium géré par Azure.

On va déjà se connecter à Azure :

az login -u $login -p $password

On va récupérer le resource group et la location :

az group list | jq -r '.[0].name'
1-67aba2f3-littlejo-sandbox
az group list | jq -r '.[0].location'
westus

On va créer un fichier de variables (terraform.azure.tfvars) :

resource_group_name = "1-67aba2f3-littlejo-sandbox"
location = "westus"

Et un terraform.tfvars pour indiquer le bon type d’installation :

cilium              = {type = "cilium_azure"}

Je crée deux fichiers pour séparer les variables obligatoire et les variables facultatives.

On va initialiser terraform:

terraform init

Initializing the backend...
Initializing modules...
Downloading registry.terraform.io/littlejo/cilium/helm 0.4.1 for cilium...
- cilium in .terraform/modules/cilium
Downloading git::https://github.com/terraform-helm/terraform-helm.git?ref=0.1 for cilium.helm...
- cilium.helm in .terraform/modules/cilium.helm
Downloading git::https://github.com/littlejo/terraform-helm-images-set-values.git?ref=v0.2 for cilium.main_image...
- cilium.main_image in .terraform/modules/cilium.main_image
Downloading git::https://github.com/littlejo/terraform-helm-images-set-values.git?ref=v0.2 for cilium.operator_image...
- cilium.operator_image in .terraform/modules/cilium.operator_image
Downloading git::https://github.com/littlejo/terraform-helm-images-set-values.git?ref=v0.2 for cilium.preflight_image...
- cilium.preflight_image in .terraform/modules/cilium.preflight_image

Initializing provider plugins...
- terraform.io/builtin/terraform is built in to Terraform
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Reusing previous version of hashicorp/local from the dependency lock file
- Reusing previous version of hashicorp/helm from the dependency lock file
- Installing hashicorp/azurerm v3.77.0...
- Installed hashicorp/azurerm v3.77.0 (signed by HashiCorp)
- Installing hashicorp/local v2.4.0...
- Installed hashicorp/local v2.4.0 (signed by HashiCorp)
- Installing hashicorp/helm v2.11.0...
- Installed hashicorp/helm v2.11.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

On va planifier :

terraform plan -var-file terraform.azure.tfvars -var-file terraform.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# azurerm_kubernetes_cluster.this will be created
+ resource "azurerm_kubernetes_cluster" "this" {
[...]
# azurerm_subnet.node will be created
+ resource "azurerm_subnet" "node" {
[...]
# azurerm_subnet.pod[0] will be created
+ resource "azurerm_subnet" "pod" {
[...]
# azurerm_virtual_network.this will be created
+ resource "azurerm_virtual_network" "this" {
[...]
# local_file.this will be created
+ resource "local_file" "this" {
[...]
Plan: 5 to add, 0 to change, 0 to destroy.

Changes to Outputs:
+ kube_config_raw = (sensitive value)
+ kube_host = (known after apply)

On retrouve les 5 ressources qu’on a vu précédemment pour cilium géré par Azure : le vnet, les deux sous-réseaux (nodes et pods), aks et le fichier kubeconfig.

Il suffit donc d’appliquer :

terraform apply -var-file terraform.azure.tfvars -var-file terraform.tfvars
[...]
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

On doit attendre environ 5 minutes notamment pour la création du cluster AKS :

azurerm_virtual_network.this: Creating...
azurerm_virtual_network.this: Creation complete after 9s [id=/subscriptions/28e1e42a-4438-4c30-9a5f-7d7b488fd883/resourceGroups/1-67aba2f3-playground-sandbox/providers/Microsoft.Network/virtualNetworks/cilium-tf-helm]
azurerm_subnet.node: Creating...
azurerm_subnet.pod[0]: Creating...
azurerm_subnet.pod[0]: Creation complete after 8s [id=/subscriptions/28e1e42a-4438-4c30-9a5f-7d7b488fd883/resourceGroups/1-67aba2f3-playground-sandbox/providers/Microsoft.Network/virtualNetworks/cilium-tf-helm/subnets/podsubnet]
azurerm_subnet.node: Still creating... [10s elapsed]
azurerm_subnet.node: Creation complete after 15s [id=/subscriptions/28e1e42a-4438-4c30-9a5f-7d7b488fd883/resourceGroups/1-67aba2f3-playground-sandbox/providers/Microsoft.Network/virtualNetworks/cilium-tf-helm/subnets/nodesubnet]
azurerm_kubernetes_cluster.this: Creating...
azurerm_kubernetes_cluster.this: Still creating... [10s elapsed]
azurerm_kubernetes_cluster.this: Still creating... [20s elapsed]
[...]
azurerm_kubernetes_cluster.this: Still creating... [4m50s elapsed]
azurerm_kubernetes_cluster.this: Creation complete after 4m51s [id=/subscriptions/28e1e42a-4438-4c30-9a5f-7d7b488fd883/resourceGroups/1-67aba2f3-playground-sandbox/providers/Microsoft.ContainerService/managedClusters/cilium-cluster-tf-helm]
local_file.this: Creating...
local_file.this: Creation complete after 0s [id=6a19c6a826e8510aa008518b0d4eae277a711136]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:

kube_config_raw = <sensitive>
kube_host = "https://cilium-3f1gy03n.hcp.westus.azmk8s.io:443"

Ça reste quand même beaucoup plus court que sur EKS. Cela prend plutôt 10 à 15 minutes. C’est assez appréciable pour faire des tests.

On peut jouer avec kubectl maintenant :

export KUBECONFIG=./kubeconfig
kubectl get node
NAME STATUS ROLES AGE VERSION
aks-default-83016694-vmss000000 Ready agent 8m13s v1.27.3
aks-default-83016694-vmss000001 Ready agent 8m14s v1.27.3
aks-default-83016694-vmss000002 Ready agent 8m15s v1.27.3

Comment est installé Cilium ?

Regardons maintenant comment est déployé cilium géré par Azure avec l’outil sympathique cilium-cli :

cilium status
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: disabled (using embedded mode)
\__/¯¯\__/ Hubble Relay: disabled
\__/ ClusterMesh: disabled

Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
Containers: cilium Running: 3
cilium-operator Running: 2
Cluster Pods: 12/12 managed by Cilium
Helm chart version: 0.1.0-90b9490f63da2433d4163ac472160844cff0cc3e
Image versions cilium mcr.microsoft.com/oss/cilium/cilium:1.12.10: 3
cilium-operator mcr.microsoft.com/oss/cilium/operator-generic:1.12.10: 2

On voit que la version de cilium n’est pas toute récente : 1.12.10. Elle a un an.

Comme première différence on voit que la helm chart version n’est pas la même que l’officielle. On voit les différentes options qui ont été faites pour installer cilium :

helm get values -n kube-system cilium
USER-SUPPLIED VALUES:
Cilium:
EnableIPv6: false
KubeClientExponentialBackoff: true
OperatorHighAvailability: true
PolicyEnforcementMode: default
global:
commonGlobals:
CIDR:
Cluster: 10.241.0.0/16
CloudEnvironment: AZUREPUBLICCLOUD
Versions:
Kubernetes: 1.27.3
lastReconciliation: "2023-11-09T15:32:16Z"

Avec :

helm get all -n kube-system cilium

On peut voir tout ce qui a été installé.

Avec cette commande on va voir assez rapidement ce qui est installé :

kubectl exec -n kube-system ds/cilium -c cilium-agent  -- cilium status --verbose
KVStore: Ok Disabled
Kubernetes: Ok 1.27 (v1.27.3) [linux/amd64]
Kubernetes APIs: ["cilium/v2::CiliumClusterwideNetworkPolicy", "cilium/v2::CiliumEndpoint", "cilium/v2::CiliumNetworkPolicy", "cilium/v2::CiliumNode", "core/v1::Namespace", "core/v1::Node", "core/v1::Pods", "core/v1::Service", "discovery/v1::EndpointSlice", "networking.k8s.io/v1::NetworkPolicy"]
KubeProxyReplacement: Strict [eth0 10.240.0.5 (Direct Routing)]
Host firewall: Disabled
CNI Chaining: none
Cilium: Ok 1.12.10 (v1.12.10-628b5209ef)
NodeMonitor: Disabled
Cilium health daemon: Ok
IPAM: IPv4: delegated to plugin,
Allocated addresses:
BandwidthManager: Disabled
Host Routing: Legacy
Masquerading: Disabled
Clock Source for BPF: ktime
Controller Status: 25/25 healthy
Name Last success Last error Count Message
dns-garbage-collector-job 26s ago never 0 no error
endpoint-13-regeneration-recovery never never 0 no error
endpoint-2017-regeneration-recovery never never 0 no error
endpoint-3213-regeneration-recovery never never 0 no error
endpoint-453-regeneration-recovery never never 0 no error
endpoint-gc 3m26s ago never 0 no error
ipcache-inject-labels 18m20s ago 18m25s ago 0 no error
k8s-heartbeat 26s ago never 0 no error
metricsmap-bpf-prom-sync 1s ago never 0 no error
neighbor-table-refresh 15s ago never 0 no error
resolve-identity-13 35s ago never 0 no error
resolve-identity-2017 3m15s ago never 0 no error
resolve-identity-3213 3m0s ago never 0 no error
resolve-identity-453 3m5s ago never 0 no error
sync-endpoints-and-host-ips 15s ago never 0 no error
sync-lb-maps-with-k8s-services 18m15s ago never 0 no error
sync-policymap-13 34s ago never 0 no error
sync-policymap-2017 3s ago never 0 no error
sync-policymap-3213 0s ago never 0 no error
sync-policymap-453 3s ago never 0 no error
sync-to-k8s-ciliumendpoint (13) 5s ago never 0 no error
sync-to-k8s-ciliumendpoint (2017) 5s ago never 0 no error
sync-to-k8s-ciliumendpoint (3213) 0s ago never 0 no error
sync-to-k8s-ciliumendpoint (453) 4s ago never 0 no error
template-dir-watcher never never 0 no error
Proxy Status: No managed proxy redirect
Global Identity Range: min 256, max 65535
Hubble: Disabled
KubeProxyReplacement Details:
Status: Strict
Socket LB: Enabled
Socket LB Coverage: Hostns-only
Socket LB Protocols: TCP, UDP
Devices: eth0 10.240.0.5 (Direct Routing)
Mode: SNAT
Backend Selection: Random
Session Affinity: Enabled
Graceful Termination: Enabled
NAT46/64 Support: Disabled
XDP Acceleration: Disabled
Services:
- ClusterIP: Enabled
- NodePort: Enabled (Range: 30000-32767)
- LoadBalancer: Enabled
- externalIPs: Enabled
- HostPort: Enabled
BPF Maps: dynamic sizing: on (ratio: 0.002500)
Name Size
Non-TCP connection tracking 65536
TCP connection tracking 131072
Endpoint policy 65535
Events 2
IP cache 512000
IP masquerading agent 16384
IPv4 fragmentation 8192
IPv4 service 65536
IPv6 service 65536
IPv4 service backend 65536
IPv6 service backend 65536
IPv4 service reverse NAT 65536
IPv6 service reverse NAT 65536
Metrics 1024
NAT 131072
Neighbor table 131072
Global policy 16384
Per endpoint policy 65536
Session affinity 65536
Signal 2
Sockmap 65535
Sock reverse NAT 65536
Tunnel 65536
Encryption: Disabled
Cluster health: Probe disabled

kube-proxy n’a pas été installé, il est géré par Cilium, ça c’est pas mal. Par contre :

  • Pas d’ebpf host routing
  • Pas de chiffrement
  • Pas de Hubble

D’autres limitations sont répertoriés ici : https://learn.microsoft.com/en-us/azure/aks/azure-cni-powered-by-cilium#limitations

  • Pas de régles L7
  • Les cilium network policies ne sont pas pris en charge.
  • Pas d’installation sous Windows
  • Pas de customisation possible

J’imagine que ça s’améliorera mais ça reste un peu décevant quand on a joué avec un cilium “officiel”.

Voilà ce qu’on peut dire sur les différentes ressources azure que j’ai utilisé et comment est géré Cilium avec la version Azure. Dans la prochaine partie, nous allons rajouter Gateway API au module helm. Cela permettra de voir comment rajouter une fonctionnalité à ce module.

Contactez-moi sur Linkedin.

--

--