Aller au contenu principal

Projet 8 : Déploiement Multi-Cloud


Contexte

Votre entreprise adopte une stratégie multi-cloud pour éviter le vendor lock-in et améliorer la résilience. Vous devez déployer l'infrastructure sur AWS (primaire) et Azure (secondaire).

Exigences

  • Infrastructure miroir sur AWS et Azure
  • DNS failover automatique
  • Base de données répliquée
  • CDN global
  • Monitoring unifié

Architecture cible


Structure du projet

projet-multi-cloud/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── providers.tf
├── aws/
│ ├── main.tf
│ ├── vpc.tf
│ ├── eks.tf
│ ├── rds.tf
│ └── outputs.tf
├── azure/
│ ├── main.tf
│ ├── vnet.tf
│ ├── aks.tf
│ ├── postgres.tf
│ └── outputs.tf
├── cloudflare/
│ ├── main.tf
│ └── dns.tf
└── modules/
├── aws-network/
├── azure-network/
└── kubernetes-app/

Implémentation

versions.tf

terraform {
required_version = ">= 1.5.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.75"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.23"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.11"
}
}
}

providers.tf

# AWS Provider
provider "aws" {
region = var.aws_region

default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
Cloud = "AWS"
}
}
}

# Azure Provider
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}

subscription_id = var.azure_subscription_id
tenant_id = var.azure_tenant_id
}

# CloudFlare Provider
provider "cloudflare" {
api_token = var.cloudflare_api_token
}

# Kubernetes Provider pour AWS
provider "kubernetes" {
alias = "aws"

host = module.aws.eks_endpoint
cluster_ca_certificate = base64decode(module.aws.eks_ca_certificate)
token = data.aws_eks_cluster_auth.main.token
}

# Kubernetes Provider pour Azure
provider "kubernetes" {
alias = "azure"

host = module.azure.aks_host
client_certificate = base64decode(module.azure.aks_client_certificate)
client_key = base64decode(module.azure.aks_client_key)
cluster_ca_certificate = base64decode(module.azure.aks_ca_certificate)
}

variables.tf

variable "project_name" {
description = "Project name"
type = string
}

variable "environment" {
description = "Environment"
type = string
}

variable "domain" {
description = "Domain name"
type = string
}

# AWS Variables
variable "aws_region" {
description = "AWS region"
type = string
default = "eu-west-1"
}

variable "aws_vpc_cidr" {
description = "AWS VPC CIDR"
type = string
default = "10.0.0.0/16"
}

# Azure Variables
variable "azure_region" {
description = "Azure region"
type = string
default = "westeurope"
}

variable "azure_subscription_id" {
description = "Azure subscription ID"
type = string
}

variable "azure_tenant_id" {
description = "Azure tenant ID"
type = string
}

variable "azure_vnet_cidr" {
description = "Azure VNet CIDR"
type = string
default = "10.1.0.0/16"
}

# CloudFlare Variables
variable "cloudflare_zone_id" {
description = "CloudFlare zone ID"
type = string
}

variable "cloudflare_api_token" {
description = "CloudFlare API token"
type = string
sensitive = true
}

# Failover Configuration
variable "primary_cloud" {
description = "Primary cloud provider"
type = string
default = "aws"

validation {
condition = contains(["aws", "azure"], var.primary_cloud)
error_message = "Primary cloud must be aws or azure."
}
}

variable "enable_failover" {
description = "Enable automatic failover"
type = bool
default = true
}

main.tf

locals {
name_prefix = "${var.project_name}-${var.environment}"
}

# AWS Infrastructure
module "aws" {
source = "./aws"

project_name = var.project_name
environment = var.environment
region = var.aws_region
vpc_cidr = var.aws_vpc_cidr

# Configuration du cluster
eks_version = "1.28"
node_instance_type = "t3.large"
min_nodes = 2
max_nodes = 10

# Base de données
db_instance_class = "db.t3.medium"
db_multi_az = true
}

# Azure Infrastructure
module "azure" {
source = "./azure"

project_name = var.project_name
environment = var.environment
location = var.azure_region
vnet_cidr = var.azure_vnet_cidr

# Configuration du cluster
aks_version = "1.28"
node_vm_size = "Standard_D2s_v3"
min_node_count = 2
max_node_count = 10

# Base de données
postgres_sku = "GP_Gen5_2"
postgres_version = "15"
}

# CloudFlare DNS & Load Balancing
module "cloudflare" {
source = "./cloudflare"

zone_id = var.cloudflare_zone_id
domain = var.domain
environment = var.environment

aws_endpoint = module.aws.alb_dns_name
azure_endpoint = module.azure.app_gateway_fqdn

primary_cloud = var.primary_cloud
enable_failover = var.enable_failover

# Health check endpoints
health_check_path = "/health"
}

# Déployer l'application sur les deux clusters
module "app_aws" {
source = "./modules/kubernetes-app"

providers = {
kubernetes = kubernetes.aws
helm = helm.aws
}

app_name = "myapp"
namespace = "production"
replicas = 3
image = var.app_image

database_host = module.aws.rds_endpoint
database_name = var.db_name

environment_variables = {
CLOUD_PROVIDER = "aws"
REGION = var.aws_region
}
}

module "app_azure" {
source = "./modules/kubernetes-app"

providers = {
kubernetes = kubernetes.azure
helm = helm.azure
}

app_name = "myapp"
namespace = "production"
replicas = 3
image = var.app_image

database_host = module.azure.postgres_fqdn
database_name = var.db_name

environment_variables = {
CLOUD_PROVIDER = "azure"
REGION = var.azure_region
}
}

aws/eks.tf

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"

cluster_name = "${local.name_prefix}-eks"
cluster_version = var.eks_version

vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets

cluster_endpoint_public_access = true
cluster_endpoint_private_access = true

eks_managed_node_groups = {
general = {
name = "${local.name_prefix}-general"

instance_types = [var.node_instance_type]
capacity_type = "ON_DEMAND"

min_size = var.min_nodes
max_size = var.max_nodes
desired_size = var.min_nodes

labels = {
Environment = var.environment
Cloud = "aws"
}
}
}

# Addons
cluster_addons = {
coredns = {
most_recent = true
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
}
aws-ebs-csi-driver = {
most_recent = true
}
}

tags = {
Name = "${local.name_prefix}-eks"
}
}

azure/aks.tf

resource "azurerm_kubernetes_cluster" "main" {
name = "${local.name_prefix}-aks"
location = var.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = local.name_prefix
kubernetes_version = var.aks_version

default_node_pool {
name = "general"
vm_size = var.node_vm_size
enable_auto_scaling = true
min_count = var.min_node_count
max_count = var.max_node_count
vnet_subnet_id = azurerm_subnet.aks.id

node_labels = {
Environment = var.environment
Cloud = "azure"
}
}

identity {
type = "SystemAssigned"
}

network_profile {
network_plugin = "azure"
network_policy = "calico"
load_balancer_sku = "standard"
}

azure_policy_enabled = true

oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}

tags = {
Name = "${local.name_prefix}-aks"
Environment = var.environment
}
}

# Application Gateway pour Ingress
resource "azurerm_application_gateway" "main" {
name = "${local.name_prefix}-appgw"
location = var.location
resource_group_name = azurerm_resource_group.main.name

sku {
name = "Standard_v2"
tier = "Standard_v2"
capacity = 2
}

gateway_ip_configuration {
name = "gateway-ip-config"
subnet_id = azurerm_subnet.appgw.id
}

frontend_port {
name = "https"
port = 443
}

frontend_ip_configuration {
name = "public"
public_ip_address_id = azurerm_public_ip.appgw.id
}

backend_address_pool {
name = "aks-backend"
}

backend_http_settings {
name = "http-settings"
cookie_based_affinity = "Disabled"
port = 80
protocol = "Http"
request_timeout = 30
probe_name = "health-probe"
}

probe {
name = "health-probe"
host = "localhost"
interval = 30
timeout = 30
unhealthy_threshold = 3
protocol = "Http"
path = "/health"
}

http_listener {
name = "https-listener"
frontend_ip_configuration_name = "public"
frontend_port_name = "https"
protocol = "Https"
ssl_certificate_name = "ssl-cert"
}

request_routing_rule {
name = "routing-rule"
rule_type = "Basic"
http_listener_name = "https-listener"
backend_address_pool_name = "aks-backend"
backend_http_settings_name = "http-settings"
priority = 100
}

ssl_certificate {
name = "ssl-cert"
key_vault_secret_id = azurerm_key_vault_certificate.main.secret_id
}

tags = {
Name = "${local.name_prefix}-appgw"
}
}

cloudflare/dns.tf

# Pool AWS
resource "cloudflare_load_balancer_pool" "aws" {
account_id = var.cloudflare_account_id
name = "${var.environment}-aws-pool"

origins {
name = "aws-origin"
address = var.aws_endpoint
enabled = true
weight = var.primary_cloud == "aws" ? 1.0 : 0.5
}

notification_email = var.alert_email

monitor = cloudflare_load_balancer_monitor.http.id
}

# Pool Azure
resource "cloudflare_load_balancer_pool" "azure" {
account_id = var.cloudflare_account_id
name = "${var.environment}-azure-pool"

origins {
name = "azure-origin"
address = var.azure_endpoint
enabled = true
weight = var.primary_cloud == "azure" ? 1.0 : 0.5
}

notification_email = var.alert_email

monitor = cloudflare_load_balancer_monitor.http.id
}

# Health Check Monitor
resource "cloudflare_load_balancer_monitor" "http" {
account_id = var.cloudflare_account_id
type = "https"
expected_body = ""
expected_codes = "2xx"
method = "GET"
timeout = 5
path = var.health_check_path
interval = 60
retries = 2
description = "Health check for ${var.environment}"

header {
header = "Host"
values = [var.domain]
}
}

# Load Balancer
resource "cloudflare_load_balancer" "main" {
zone_id = var.zone_id
name = "api.${var.domain}"
fallback_pool_id = cloudflare_load_balancer_pool.aws.id
default_pool_ids = var.primary_cloud == "aws" ? [
cloudflare_load_balancer_pool.aws.id,
cloudflare_load_balancer_pool.azure.id
] : [
cloudflare_load_balancer_pool.azure.id,
cloudflare_load_balancer_pool.aws.id
]

proxied = true
ttl = 30
steering_policy = var.enable_failover ? "dynamic_latency" : "off"

# Règles de failover par région
dynamic "rules" {
for_each = var.enable_failover ? [1] : []
content {
name = "geo-steering"

overrides {
steering_policy = "geo"

region_pools {
region = "WNAM" # West North America
pool_ids = [cloudflare_load_balancer_pool.aws.id]
}

region_pools {
region = "ENAM" # East North America
pool_ids = [cloudflare_load_balancer_pool.aws.id]
}

region_pools {
region = "WEU" # West Europe
pool_ids = [cloudflare_load_balancer_pool.azure.id]
}

region_pools {
region = "EEU" # East Europe
pool_ids = [cloudflare_load_balancer_pool.azure.id]
}
}
}
}
}

# Page Rules pour le caching
resource "cloudflare_page_rule" "cache_static" {
zone_id = var.zone_id
target = "static.${var.domain}/*"
priority = 1

actions {
cache_level = "cache_everything"
edge_cache_ttl = 86400
browser_cache_ttl = 3600
}
}

outputs.tf

output "aws_eks_endpoint" {
description = "AWS EKS cluster endpoint"
value = module.aws.eks_endpoint
}

output "azure_aks_host" {
description = "Azure AKS cluster host"
value = module.azure.aks_host
}

output "application_url" {
description = "Application URL via CloudFlare"
value = "https://api.${var.domain}"
}

output "aws_rds_endpoint" {
description = "AWS RDS endpoint"
value = module.aws.rds_endpoint
}

output "azure_postgres_fqdn" {
description = "Azure PostgreSQL FQDN"
value = module.azure.postgres_fqdn
}

output "cloudflare_load_balancer" {
description = "CloudFlare Load Balancer status"
value = {
primary_pool = var.primary_cloud
failover = var.enable_failover
dns_name = cloudflare_load_balancer.main.name
}
}

Réplication de données

Cross-Cloud Database Replication

# Utiliser un outil comme Bucardo ou pglogical pour la réplication
# Exemple avec AWS DMS pour la réplication initiale

resource "aws_dms_replication_instance" "cross_cloud" {
replication_instance_id = "${local.name_prefix}-cross-cloud"
replication_instance_class = "dms.r5.large"
allocated_storage = 50
vpc_security_group_ids = [aws_security_group.dms.id]
replication_subnet_group_id = aws_dms_replication_subnet_group.main.id

tags = {
Name = "${local.name_prefix}-cross-cloud-dms"
}
}

resource "aws_dms_endpoint" "azure_target" {
endpoint_id = "${local.name_prefix}-azure-target"
endpoint_type = "target"
engine_name = "postgres"

server_name = module.azure.postgres_fqdn
port = 5432
database_name = var.db_name
username = var.db_username
password = var.db_password
ssl_mode = "require"
}

Coûts estimés

RessourceAWSAzure
Kubernetes (EKS/AKS)~$150~$150
Nodes (2x medium)~$100~$100
Load Balancer~$20~$30
Database~$100~$100
CloudFlare LB~$200-
Total~$370~$380

← Projet 7 | Troubleshooting Avancé →