Dans la partie précédente de cette série, nous avons expliqué les bases du concept de Remote State dans Terraform et comment il peut être utilisé pour l'héritage d'informations dans des environnements multi-tenants. Nous allons maintenant illustrer cela à l'aide d'un exemple concret d'architecture.
Introduction
Une architecture multi-tenants efficace doit suivre la structure organisationnelle d'une entreprise tout en respectant les bonnes pratiques du cloud. Les plateformes cloud modernes offrent différentes possibilités pour représenter des structures organisationnelles hiérarchiques - que ce soit par le biais de Compartments (OCI), Organizations et Accounts (AWS), Groupes de ressources (Azure) ou Projets et Dossiers (GCP).
Présentation du concept d'architecture
Dans notre exemple d'architecture, nous considérons un "Datacenter mondial du client" comme l'unité organisationnelle supérieure. Au sein de cette unité, il existe différentes zones :
Systèmes et services mondiaux : Ceux-ci incluent IAM, la surveillance, la facturation et d'autres services centraux.
Datacenters régionaux : Infrastructures géographiquement distribuées pour répondre à des besoins variés.
Divisions organisationnelles : Par exemple, selon les départements, les zones de sécurité ou d'autres critères.
La particularité de ce modèle : Chaque unité organisationnelle représente son propre domaine logique avec une responsabilité définie. Ces unités forment des frontières naturelles entre différents locataires ou unités organisationnelles tout en offrant un cadre pour l'héritage des autorisations et des configurations.
Organisation hiérarchique comme modèle naturel de multi-tenancy
La structuration hiérarchique de l'infrastructure cloud offre plusieurs avantages pour les scénarios de multi-tenancy :
Organisation hiérarchique : Les unités peuvent être imbriquées, semblables à une structure de dossiers.
Héritage des politiques : Les autorisations sont héritées des niveaux supérieurs vers les niveaux inférieurs.
Gestion des ressources : Des limites peuvent être définies pour chaque unité organisationnelle.
Suivi des coûts : La facturation et la mesure de la consommation sont effectuées au niveau des unités organisationnelles.
Un exemple : Une entreprise pourrait avoir une unité de niveau supérieur pour chaque unité commerciale. Au sein de ces unités, d'autres sous-unités pourraient être définies pour la production, le développement et les tests. Au niveau le plus bas, des unités spécifiques aux projets pourraient être présentes.
Cette hiérarchie reflète la structure organisationnelle tout en facilitant la gestion des droits d'accès et des ressources. Par exemple, les administrateurs peuvent obtenir l'accès à une unité de département spécifique sans pour autant avoir accès aux autres départements.
Niveaux de segmentation différents
Nous pouvons segmenter notre infrastructure selon différentes dimensions :
Segmentation technique : Par exemple, un "Datacenter régional du site A" avec différentes zones techniques (DC1, DC2, DC3, DC4).
Segmentation organisationnelle : Par exemple, un "Datacenter régional du site B" avec différentes divisions (Div. 1, Div. 2, etc.).
Zones de sécurité : Par exemple, un "Datacenter régional du site n" avec des zones de haute sécurité, moyenne, basse et publique.
Value Streams : Processus métier pouvant traverser d'autres structures.
Ces différentes dimensions de segmentation peuvent se chevaucher et se compléter. Le défi consiste à les représenter proprement dans Terraform tout en permettant les flux d'informations entre elles.
Particulièrement importante est l'héritage des données via le Remote State. Ici, nous observons comment les informations circulent des niveaux supérieurs (Datacenter global) vers les niveaux inférieurs (datacenters régionaux et leurs sous-unités). Cela correspond exactement au modèle Remote-State que nous avons abordé dans la section précédente.
Mise en œuvre pratique à l'aide de modules Terraform
La mise en œuvre d'une telle architecture dans Terraform nécessite une approche modulaire structurée. Pour chaque niveau de la hiérarchie, nous créons des projets Terraform dédiés.
Remarque : La configuration exacte de la source de données Remote State varie en fonction du backend utilisé. Des ajustements appropriés sont nécessaires pour les options courantes telles que S3, GCS ou Consul.
1. Module racine pour l'infrastructure globale :
# 1. Infrastructure globale (niveau racine) module "global_datacenter" { source = "./modules/organization" name = "global-datacenter-customer" description = "Datacenter global du client" parent_id = var.root_organization_id } module "global_network" { source = "./modules/network" organization_id = module.global_datacenter.id cidr_block = "10.0.0.0/16" name = "global-network" } # Systèmes et services globaux nécessaires module "global_services" { source = "./modules/organization" name = "global-services" description = "Systèmes et services globaux requis pour le client" parent_id = module.global_datacenter.id } # Les modules IAM, Monitoring et Billing Services seraient implémentés ici... output "global_datacenter_id" { value = module.global_datacenter.id } output "global_network_id" { value = module.global_network.id } output "global_services_id" { value = module.global_services.id }
2. Infrastructure régionale selon la segmentation technique :
# 2. Datacenter régional dans la Localisation A (plusieurs DCs) data "terraform_remote_state" "global" { backend = "remote" config = { organization = "my-org" workspaces = { name = "global-infrastructure" } } } module "location_a" { source = "./modules/organization" name = "location-a-datacenter-regional" description = "Datacenter régional Localisation A" parent_id = data.terraform_remote_state.global.outputs.global_datacenter_id } # Provisionnement des 4 DCs comme sous-unités module "dc1" { source = "./modules/organization" name = "dc1" description = "Datacenter 1" parent_id = module.location_a.id } module "dc2" { source = "./modules/organization" name = "dc2" description = "Datacenter 2" parent_id = module.location_a.id } module "dc3" { source = "./modules/organization" name = "dc3" description = "Datacenter 3" parent_id = module.location_a.id } module "dc4" { source = "./modules/organization" name = "dc4" description = "Datacenter 4" parent_id = module.location_a.id } # Sous-réseaux du réseau global pour la Localisation A module "subnet_dc1" { source = "./modules/subnet" organization_id = module.dc1.id network_id = data.terraform_remote_state.global.outputs.global_network_id cidr_block = "10.0.1.0/24" name = "subnet-dc1" } # Plus de sous-réseaux pour DC2, DC3, DC4... # Outputs à utiliser dans d'autres projets Terraform output "location_a_id" { value = module.location_a.id } output "dc1_id" { value = module.dc1.id } output "subnet_dc1_id" { value = module.subnet_dc1.id } # Sorties supplémentaires
3. Infrastructure régionale selon la segmentation organisationnelle :
# 3. Datacenter régional pour la Localisation B (Organigramme) data "terraform_remote_state" "global" { backend = "remote" config = { organization = "my-org" workspaces = { name = "global-infrastructure" } } } module "location_b" { source = "./modules/organization" name = "location-b-datacenter-regional" description = "Datacenter régional Localisation B" parent_id = data.terraform_remote_state.global.outputs.global_datacenter_id } # Départements organisationnels module "department_1" { source = "./modules/organization" name = "department-1" description = "Département 1" parent_id = module.location_b.id } module "department_2" { source = "./modules/organization" name = "department-2" description = "Département 2" parent_id = module.location_b.id } module "department_3" { source = "./modules/organization" name = "department-3" description = "Département 3" parent_id = module.location_b.id } module "department_4" { source = "./modules/organization" name = "department-4" description = "Département 4" parent_id = module.location_b.id } # Sous-réseaux spécifiques aux départements module "subnet_department_1" { source = "./modules/subnet" organization_id = module.department_1.id network_id = data.terraform_remote_state.global.outputs.global_network_id cidr_block = "10.0.10.0/24" name = "subnet-department-1" } # Outputs pour d'autres projets Terraform output "location_b_id" { value = module.location_b.id } output "department_1_id" { value = module.department_1.id } output "subnet_department_1_id" { value = module.subnet_department_1.id }
4. Infrastructure régionale selon la segmentation de sécurité :
# 4. Datacenter régional pour la Localisation n (Zones de sécurité) data "terraform_remote_state" "global" { backend = "remote" config = { organization = "my-org" workspaces = { name = "global-infrastructure" } } } module "location_n" { source = "./modules/organization" name = "location-n-datacenter-regional" description = "Datacenter régional Localisation n" parent_id = data.terraform_remote_state.global.outputs.global_datacenter_id } # Zones de sécurité en tant que sous-unités module "high" { source = "./modules/organization" name = "high" description = "Zone de haute sécurité" parent_id = module.location_n.id } module "med" { source = "./modules/organization" name = "med" description = "Zone de sécurité moyenne" parent_id = module.location_n.id } module "low" { source = "./modules/organization" name = "low" description = "Zone de faible sécurité" parent_id = module.location_n.id } module "pub" { source = "./modules/organization" name = "pub" description = "Zone publique" parent_id = module.location_n.id } # Sous-réseaux spécifiques aux zones de sécurité module "subnet_high" { source = "./modules/subnet" organization_id = module.high.id network_id = data.terraform_remote_state.global.outputs.global_network_id cidr_block = "10.0.20.0/24" name = "subnet-high" prohibit_public_ip = true } # Ressources de sécurité supplémentaires comme les Security Lists et Network Security Groups... # Outputs output "location_n_id" { value = module.location_n.id } output "high_security_id" { value = module.high.id } output "subnet_high_id" { value = module.subnet_high.id }
5. Infrastructure spécifique à un département ou une équipe :
# 5. Exemple de ressources dans un département utilisant les informations du Remote State data "terraform_remote_state" "standort_b" { backend = "remote" config = { organization = "my-org" workspaces = { name = "regional-location-b" } } } module "vm_instances" { source = "./modules/compute" instances = { app_server_department_1 = { organization_id = data.terraform_remote_state.location_b.outputs.department_1_id subnet_id = data.terraform_remote_state.location_b.outputs.subnet_department_1_id size = "medium" image = var.app_image_id } # Machines virtuelles supplémentaires... } }
Configuration dynamique des modules pour des modèles de déploiement flexibles
Pour accroître encore davantage la flexibilité, nous pouvons également travailler avec des configurations dynamiques. Cela est particulièrement utile lorsque nous devons gérer un grand nombre de ressources similaires :
module "regional_datacenters" { source = "./modules/regional_datacenter" for_each = var.datacenter_configs name = each.key description = each.value["description"] parent_id = data.terraform_remote_state.global.outputs.global_datacenter_id network_cidr = each.value["network_cidr"] security_level = each.value["security_level"] subunits = each.value["subunits"] } # Exemple d'utilisation : Comment appeler ce module depuis un module racine # datacenter_configs = { # "europe-west" = { # description = "Datacenter européen - Région Ouest" # network_cidr = "10.1.0.0/16" # security_level = "high" # subunits = ["prod", "staging", "dev", "test"] # }, # "us-east" = { # description = "Datacenter de la côte Est des États-Unis" # network_cidr = "10.2.0.0/16" # security_level = "medium" # subunits = ["prod", "dev"] # } # }
Avantages de la structure modulaire
Cette structure modulaire nous permet de traduire la hiérarchie logique de notre organisation en projets Terraform distincts, tout en permettant l'échange d'informations entre eux :
Responsabilités claires : Chaque équipe gère son propre code Terraform et ses propres States.
Héritage automatique des informations : L'équipe qui gère l'infrastructure globale peut effectuer des modifications sans que les équipes au niveau régional ou départemental n'aient à adapter leur code.
Déploiements isolés : Les erreurs dans une zone n'affectent pas automatiquement les autres zones.
Évolutivité : De nouvelles unités organisationnelles peuvent être ajoutées facilement sans modifier les structures existantes.
Gouvernance cohérente : Grâce à l'héritage des Outputs, des configurations homogènes sont assurées à tous les niveaux.
Exemple d'une configuration complète en environnement multi-tenant
Voici un exemple illustrant comment ces modèles peuvent être combinés dans un projet réel :
module "tenant_environments" { source = "./modules/tenant_environment" for_each = { for tenant in var.tenants : tenant.name => tenant } name = each.key parent_id = module.tenant_root_organization.id environments = each.value["environments"] network_cidr = each.value["network_cidr"] security_policy = each.value["security_policy"] } # Les environnements de locataires peuvent ensuite être utilisés pour différents clients, # unités commerciales ou projets. # # Exemple : # tenants = [ # { # name = "finance-department" # environments = ["prod", "dev", "test"] # network_cidr = "10.10.0.0/16" # security_policy = "high" # }, # { # name = "marketing-department" # environments = ["prod", "staging"] # network_cidr = "10.20.0.0/16" # security_policy = "medium" # } # ]
Avec cette approche structurée, nous pouvons gérer des environnements multi-tenants complexes d'une manière à la fois techniquement robuste et organisationnellement logique. La séparation claire des responsabilités, combinée à l'héritage ciblé des informations via les Remote States, nous permet de travailler efficacement avec Terraform même dans des équipes larges et distribuées.
Sécurité des informations Remote State
Aussi utiles que soient les Remote States, ils peuvent potentiellement contenir des informations sensibles. La protection de ces informations est essentielle, en particulier dans les environnements multi-tenants.
Pour garantir que seules les équipes autorisées puissent accéder aux Outputs des Remote States, vous devez mettre en œuvre les mesures suivantes :
- Restriction des accès : Utilisez les mécanismes de contrôle d'accès de votre backend (par ex. les politiques IAM pour S3, les ACLs pour Consul ou les contrôles d'accès basés sur les rôles dans Terraform Cloud/Enterprise).
- Minimisation des Outputs : Ne définissez que les informations strictement nécessaires comme Output. Chaque attribut supplémentaire accroît le risque de divulgation de données sensibles.
- Évitez les Outputs inutiles : Les Outputs contenant des valeurs critiques pour la sécurité (par ex. mots de passe, clés privées) ne devraient pas être fournis en tant qu'Output, mais plutôt gérés via des services de gestion de secrets comme HashiCorp Vault ou Secrets Manager.
- Regroupement des informations : Au lieu de fournir individuellement les mots de passe de bases de données, les clés API ou d'autres détails sensibles comme Outputs, privilégiez les Outputs structurés qui regroupent uniquement les paramètres pertinents.
- Utilisation de Proxy-Statefiles : Filtrez les Outputs d'un Remote State pour ne fournir que les informations pertinentes pour chaque tenant, puis exposez ces données via un nouveau State. Chaque tenant ne doit avoir accès qu'à ce Proxy-State.
Gestion des erreurs avec les dépendances Remote State
L'utilisation de Remote States dans des architectures multi-tenants présente des défis spécifiques en matière de gestion des erreurs. Des problèmes tels que des backends indisponibles, des Outputs incohérents ou des changements inattendus peuvent entraîner des complications importantes. Pour réduire ces risques, vous devez mettre en œuvre les mesures suivantes :
- Vérifications de disponibilité : Utilisez le bloc terraform_remote_state de Terraform en combinaison avec des constructions de requêtes comme lookup() pour gérer les erreurs liées aux Outputs manquants.
- Vérification des Outputs corrects : Ajoutez des tests dans vos modules et vos pipelines CI/CD pour vous assurer que les Outputs des Remote States contiennent toujours les structures et les contenus attendus.
- Mécanismes de repli : Si les Remote States sont temporairement inaccessibles, l'utilisation de valeurs mises en cache localement (par ex. via des variables d'environnement) peut constituer une solution temporaire.
Exemple d'une requête Remote-State robuste avec gestion des erreurs :
data "terraform_remote_state" "network" { backend = "remote" config = { organization = "my-org" workspaces = { name = "global-infrastructure" } } } locals { private_subnet_id = lookup(data.terraform_remote_state.network.outputs.subnets, "private", null) } resource "example_vm" "app_server" { subnet_id = local.private_subnet_id != null ? local.private_subnet_id : var.default_subnet_id }
Dans la prochaine section, nous examinerons les Remote Backends et verrons comment organiser les Remote States de manière hiérarchique.