L’infrastructure Azure que nous proposons pour déployer votre application web repose principalement sur l’utilisation de ressources publiques.
L’objectif est de vous permettre de créer l’infrastructure minimale pour faire fonctionner votre application web ; à savoir un backend, un frontend ainsi qu’une gestion des secrets. Nous déploierons par ailleurs des ressources transverses pouvant être utilisées par plusieurs de vos ressources applicatives.
Sans plus attendre, plongeons-nous dans l’architecture proposée.
Les ressources dites “transverses” ont pour objectif d’être utilisées par plusieurs applications au sein d’une même souscription. Les recommandations et Best Practices Azure incitent à déployer certaines ressources critiques pour l’ensemble de l’environnement applicatif.
C’est pourquoi nous avons un groupe de ressource dédié, dans lequel se situeront :
Le groupe de ressources applicatif est dédié à une application de notre souscription pour un environnement donné. Il pourra être dupliqué autant que nécessaire selon le nombre d’applications à déployer.
Nous déployons ici une application intégrant un frontend statique et un backend conteneurisé ayant le besoin de se connecter à une base de données et d’utiliser des secrets. Cette application doit être exposée sur internet pour permettre à vos utilisateurs d’y accéder.
Pour répondre à ce besoin nous avons donc déployé :
Aujourd’hui, le modèle d’architecture Hub & Spokes est largement utilisé dans les infrastructures Azure. Notre proposition d’infrastructure pour le déploiement d’une application web s’intègre parfaitement à ce paradigme. Il sera très facile d’exposer l’application au travers d’un load balancer frontal tel qu’une Azure Frontdoor. On pourra aussi déployer l’ensemble des ressources dans des réseaux virtuels dédiés.
Ainsi, vous n’aurez pas à chambouler l’ensemble de votre infrastructure pour y implémenter votre nouvelle application.
Dans la suite de cet article, nous allons expliquer pas à pas comment implémenter cette infrastructure via terraform, à l’aide des modules open source de la librairie Theodo Cloud.
Nous allons, dans un premier temps, déployer les ressources transverses de notre infrastructure.
Déployez tout d’abord le groupe de ressources dédié.
azurerm_resource_group "transverse" {
name = "transverse"
location = "francecentral"
}
Nous voulons ensuite mettre en place les ressources permettant de monitorer l’ensemble de notre infrastructure. Nous voulons également avoir un backup de nos données. Pour cela nous utiliserons deux modules open source de la librairie : terraform-azurerm-logger
et terraform-azurerm-storage-account
.
module "logger" {
source = "git@github.com:padok-team/terraform-azurerm-logger.git?ref=v0.2.0"
resource_group = azurerm_resource_group.transverse
name = "logger"
}
module "logger_backup" {
source = "git@github.com:padok-team/terraform-azurerm-storage-account.git?ref=v0.2.1"
resource_group = azurerm_resource_group.transverse
name = "logger-backup"
}
resource "azurerm_log_analytics_data_export_rule" "this" {
name = "logger-data-exporter"
resource_group_name = azurerm_resource_group.transverse.name
workspace_resource_id = module.logger[0].azurerm_log_analytics_workspace_id
destination_resource_id = module.backup[0].this.id
table_names = [
... # What you want to export
]
enabled = true
}
Enfin, nous allons déployer notre Container Registry et le Keyvault qui lui est associé. Cela permettra de stocker et récupérer les images conteneurisées des applications. Nous utiliserons pour cela les modules terraform-azurerm-keyvault
et terraform-azurerm-acr
de la librairie open source.
module "acr_keyvault" {
source = "git@github.com:/padok-team/terraform-azurerm-keyvault?ref=v0.2.0"
name = "acr-keyvault"
resource_group = azurerm_resource_group.transverse
sku_name = "standard"
}
resource "azurerm_key_vault_key" "acr_encryption" {
name = "acr-encryption"
key_vault_id = module.acr_keyvault.akv_id
key_type = "RSA"
key_size = 2048
key_opts = [
"decrypt",
"encrypt",
"sign",
"unwrapKey",
"verify",
"wrapKey",
]
}
module "acr" {
source = "git@github.com:/padok-team/terraform-azurerm-acr?ref=v0.4.0"
name = "acr"
resource_group_name = azurerm_resource_group.transverse.name
location = azurerm_resource_group.transverse.location
# Encryption at rest
encryption_key_vault_id = module.acr_keyvault.akv_id
encryption_key_vault_key_id = azurerm_key_vault_key.acr_encryption.id
network_default_action = "Allow"
public_network_access_enabled = true
}
Notre zone transverse est maintenant terminée et pourra être utilisée par différentes applications de notre infrastructure.
Pour cette seconde partie nous allons déployer l’ensemble des ressources nécessaires au bon fonctionnement de l’application web dans notre infrastructure.
Nous déployons dans un premier temps un groupe de ressources pour l’application.
azurerm_resource_group "app" {
name = "app"
location = "francecentral"
}
Notre application web ayant besoin d’une base de données ainsi que d’accéder à des secrets, nous allons donc utiliser les modules terraform-azurerm-postgresql-server
et terraform-azurerm-keyvault
de la librairie open source.
module "app_keyvault" {
source = "git@github.com:/padok-team/terraform-azurerm-keyvault?ref=v0.2.0"
name = "acr-keyvault"
resource_group = azurerm_resource_group.app
sku_name = "standard"
}
module "pgsql-server" {
source = "git@github.com:padok-team/terraform-azurerm-postgresql-server?ref=v0.2.0"
name = "pgsql-server"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
administrator_login = "admin"
}
# Store secret in the keyvault
resource "azurerm_key_vault_secret" "database_admin_password" {
name = "DATABASE-ADMINISTRATOR-LOGIN-PASSWORD"
key_vault_id = module.app_keyvault.akv_id
value = module.application_database.this.administrator_login_password
}
L’application web ayant besoin d’un frontend statique et d’un backend conteneurisé nous allons tout d’abord déployer les ressources nécessaires. Nous utiliserons les modules terraform-azurerm-storage-account
et terraform-azurerm-app-service-container-linux
de la librairie open source.
De plus, nous allons permettre au backend de pouvoir accéder aux images du Container Registry ainsi que de pouvoir lire les secrets d’un Keyvault.
module "backend" {
source = "git@github.com:padok-team/terraform-azurerm-app-service-container-linux.git?ref=v0.1.3"
name = "backend"
resource_group = azurerm_resource_group.app
app_settings = {
DOCKER_REGISTRY_SERVER_URL = "https://${module.acr.this.login_server}"
DATABASE_HOSTNAME = module.application_database.this.fqdn
DATABASE_ADMINISTRATOR_LOGIN_PASSWORD = "@Microsoft.KeyVault(VaultName=${module.app_keyvault.akv_name};SecretName=${azurerm_key_vault_secret.database_admin_password.name})"
DATABASE_USERNAME = module.application_database.this.administrator_login
}
enable_auth_settings = false
client_cert_enabled = false
site_config_override = {
acr_use_managed_identity_credentials = true
}
}
resource "azurerm_role_assignment" "backend_acr_pull" {
scope = module.acr.this.id
role_definition_name = "AcrPull"
principal_id = module.backend.this.identity[0].principal_id
}
resource "azurerm_role_assignment" "backend_keyvault_secret_user" {
scope = module.app_keyvault.akv_id
role_definition_name = "Key Vault Secrets User"
principal_id = module.backend.this.identity[0].principal_id
}
module "frontend" {
source = "git@github.com:padok-team/terraform-azurerm-storage-account.git?ref=v0.2.1"
name = "frontend"
resource_group = azurerm_resource_group.app
static_website_enabled = true
static_website_index_document = "index.html"
static_website_error_404_document = "index.html"
network_rules_default_action = "Allow"
}
Nous avons maintenant besoin d’une Application Gateway placée devant le storage account et l’app service afin d’effectuer le routage des requêtes entrantes. Ce sera la porte d’entrée de notre infrastructure.
Afin de permettre une communication https dans notre infrastructure, nous allons également déclarer un certificat dans le keyvault qui sera utilisé pour le chiffrement TLS et passé en paramètre à l’application gateway.
resource "azurerm_user_assigned_identity" "application_gateway" {
location = azurerm_resource_group.app.name
resource_group_name = azurerm_resource_group.app.location
name = "app-gateway"
}
resource "azurerm_role_assignment" "app_gateway_secret_officer" {
scope = module.app_keyvault.akv_id
role_definition_name = "Key Vault Secrets Officer"
principal_id = azurerm_user_assigned_identity.application_gateway.principal_id
}
resource "azurerm_application_gateway" "this" {
name = "app_gateway"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
sku {
name = "Standard_v2"
tier = "Standard_v2"
capacity = 2
}
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.application_gateway.id]
}
#----------------------------------------------------------------
# COMMON
#----------------------------------------------------------------
frontend_ip_configuration {
name = "frontend-ip-configuration"
public_ip_address = xxx
}
frontend_port {
name = "http"
port = 80
}
frontend_port {
name = "https"
port = 443
}
http_listener {
name = "http"
frontend_ip_configuration_name = "frontend-ip-configuration"
frontend_port_name = "https"
protocol = "Http"
}
http_listener {
name = "https"
frontend_ip_configuration_name = "frontend-ip-configuration"
frontend_port_name = "https"
protocol = "Https"
ssl_certificate_name = "certificate"
}
ssl_certificate {
name = "certificate"
key_vault_secret_id = "xxx"
}
request_routing_rule {
name = "http-to-https"
http_listener_name = "http"
redirect_configuration_name = "http-to-https"
rule_type = "Basic"
}
# Request Configuration for Https
redirect_configuration {
name = "http-to-https"
target_listener_name = "https"
redirect_type = "Permanent"
include_path = true
include_query_string = true
}
request_routing_rule {
name = "http-to-https"
rule_type = "PathBasedRouting"
http_listener_name = "https"
url_path_map_name = "url-path-map"
}
url_path_map {
name = "url-path-map"
default_backend_address_pool_name = "app-static-frontend"
default_backend_http_settings_name = "app-static-frontend"
default_redirect_configuration_name = null
default_rewrite_rule_set_name = null
path_rule {
name = "app-backend"
paths = ["/api/*"]
backend_address_pool_name = "app-backend"
backend_http_settings_name = "app-backend"
redirect_configuration_name = null
rewrite_rule_set_name = null
firewall_policy_id = null
}
}
#----------------------------------------------------------------
# FRONTEND
#----------------------------------------------------------------
# Backend pool for frontend storage account
backend_address_pool {
name = "app-static-frontend"
fqdns = [module.frontend.this.primary_web_host]
}
backend_http_settings {
name = "app-static-frontend"
cookie_based_affinity = "Disabled"
path = "/"
port = 443
protocol = "Https"
request_timeout = 60
probe_name = "app-static-frontend"
pick_host_name_from_backend_address = true
}
probe {
name = "app-static-frontend"
protocol = "Https"
path = "/index.html"
host = module.forntend.this.primary_web_host
port = 443
unhealthy_threshold = 3
interval = 30
timeout = 30
}
# #----------------------------------------------------------------
# # BACKEND
# #----------------------------------------------------------------
backend_address_pool {
name = "app-backend"
fqdns = [module.backend.this.default_site_hostname]
}
backend_http_settings {
name = "app-backend"
cookie_based_affinity = "Disabled"
path = "/"
port = 443
protocol = "Https"
request_timeout = 60
probe_name = "app-backend"
pick_host_name_from_backend_address = true
}
probe {
name = "app-backend"
protocol = "Https"
path = "/"
host = module.backend.this.default_site_hostname
port = 443
unhealthy_threshold = 3
interval = 30
timeout = 30
}
depends_on = [
azurerm_role_assignment.app_gateway_secret_officer
]
}
Les modules et ressources présentés vous permettront d’implémenter une infrastructure managée publique pour déployer et exposer vos applications web.
Cette proposition d’infrastructure s’inscrit totalement dans une architecture stand-alone mais est aussi adaptée à un environnement de type hub & spokes.
Enfin, de nombreuses évolutions sont possibles, il serait par exemple intéressant de privatiser l’ensemble des ressources applicatives de cette infrastructure pour améliorer la sécurité de celle-ci. Vous pouvez d’ailleurs retrouver cet article sur la privatisation d’une infrastructure hub & spokes Azure sur le blog Theodo Cloud.