Azure MySQL Single Server using Terraform
Step-00: Introduction¶
Terraform Concepts¶
- Input Variables
mysqldb.auto.tfvars - Input Variables
secrets.tfvarswith-var-fileargument
Azure Concepts¶
- Create Azure MySQL Single Server and Sample Schema in it
- Create
service endpoint policiesto allow traffic to specific azure resources from your virtual network over service endpoints - Create
Virtual Network Ruleto make a connection from Azure Virtual Network Subnet to Azure MySQL Single Server - Create a
MySQL Firewall Ruleto allow Bastion Host to access MySQL DB. Understand MySQL Firewall rule concept.
Azure Resources¶
- azurerm_mysql_server
- azurerm_mysql_database
- azurerm_mysql_firewall_rule
- azurerm_mysql_virtual_network_rule
Step-01: Azure MySQL Single Server MySQL TF Configs¶
Step-01-01: c11-01-mysql-servers-input-variables.tf¶
# Input Variables
# DB Name
variable "mysql_db_name" {
description = "Azure MySQL Database Name"
type = string
}
# DB Username - Enable Sensitive flag
variable "mysql_db_username" {
description = "Azure MySQL Database Administrator Username"
type = string
}
# DB Password - Enable Sensitive flag
variable "mysql_db_password" {
description = "Azure MySQL Database Administrator Password"
type = string
sensitive = true
}
# DB Schema Name
variable "mysql_db_schema" {
description = "Azure MySQL Database Schema Name"
type = string
}
Step-01-02: c11-02-mysql-servers-resource.tf¶
# Resource-1: Azure MySQL Server
resource "azurerm_mysql_server" "mysql_server" {
name = "${local.resource_name_prefix}-${var.mysql_db_name}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
administrator_login = var.mysql_db_username
administrator_login_password = var.mysql_db_password
#sku_name = "B_Gen5_2" # Basic Tier - Azure Virtual Network Rules not supported
sku_name = "GP_Gen5_2" # General Purpose Tier - Supports Azure Virtual Network Rules
storage_mb = 5120
version = "8.0"
auto_grow_enabled = true
backup_retention_days = 7
geo_redundant_backup_enabled = false
infrastructure_encryption_enabled = false
public_network_access_enabled = true
ssl_enforcement_enabled = false
ssl_minimal_tls_version_enforced = "TLSEnforcementDisabled"
}
# Resource-2: Azure MySQL Database / Schema
resource "azurerm_mysql_database" "webappdb" {
name = var.mysql_db_schema
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mysql_server.mysql_server.name
charset = "utf8"
collation = "utf8_unicode_ci"
}
# Resource-3: Azure MySQL Firewall Rule - Allow access from Bastion Host Public IP
resource "azurerm_mysql_firewall_rule" "mysql_fw_rule" {
name = "allow-access-from-bastionhost-publicip"
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mysql_server.mysql_server.name
start_ip_address = azurerm_public_ip.bastion_host_publicip.ip_address
end_ip_address = azurerm_public_ip.bastion_host_publicip.ip_address
}
# Resource-4: Azure MySQL Virtual Network Rule
resource "azurerm_mysql_virtual_network_rule" "mysql_virtual_network_rule" {
name = "mysql-vnet-rule"
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mysql_server.mysql_server.name
subnet_id = azurerm_subnet.websubnet.id
}
Step-01-03: c11-03-mysql-servers-output-values.tf¶
# Output Values
output "mysql_server_fqdn" {
description = "MySQL Server FQDN"
value = azurerm_mysql_server.mysql_server.fqdn
}
Step-01-04: mysqldb.auto.tfvars¶
Step-01-05: secrets.tfvars¶
Step-02: Virtual Network Subnet Changes¶
Step-02-01: c6-03-web-subnet-and-nsg.tf - Web Subnet¶
- Create service endpoint policies to allow traffic to specific azure resources from your virtual network over service endpoints.
- Add
service_endpoints = [ "Microsoft.Sql" ]for Web Subnet# Resource-1: Create WebTier Subnet resource "azurerm_subnet" "websubnet" { name = "${azurerm_virtual_network.vnet.name}-${var.web_subnet_name}" resource_group_name = azurerm_resource_group.rg.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = var.web_subnet_address service_endpoints = [ "Microsoft.Sql" ] }
Step-02-02: c6-03-web-subnet-and-nsg.tf - Locals Block¶
- Add Port 8080 for Web Subnet Inbound Port Rules in Locals Block
Step-03: Application Configs¶
Step-03-01: c7-01-web-linux-vmss-input-variables.tf¶
# Linux VM Input Variables Placeholder file.
variable "web_vmss_nsg_inbound_ports" {
description = "Web VMSS NSG Inbound Ports"
type = list(string)
default = [22, 80, 443, 8080]
}
Step-03-02: terraform.tfvars¶
Step-03-03: c7-03-web-linux-vmss-resource.tf - Locals Block Custom Data¶
# Locals Block for custom data
locals {
webvm_custom_data = <<CUSTOM_DATA
#!/bin/sh
#sudo yum update -y
# Stop Firewall and Disable it
sudo systemctl stop firewalld
sudo systemctl disable firewalld
# Java App Install
sudo yum -y install java-11-openjdk
sudo yum -y install telnet
sudo yum -y install mysql
mkdir /home/azureuser/app3-usermgmt && cd /home/azureuser/app3-usermgmt
wget https://github.com/stacksimplify/temp1/releases/download/1.0.0/usermgmt-webapp.war -P /home/azureuser/app3-usermgmt
export DB_HOSTNAME=${azurerm_mysql_server.mysql_server.fqdn}
export DB_PORT=3306
export DB_NAME=${azurerm_mysql_database.webappdb.name}
export DB_USERNAME="${azurerm_mysql_server.mysql_server.administrator_login}@${azurerm_mysql_server.mysql_server.fqdn}"
export DB_PASSWORD=${azurerm_mysql_server.mysql_server.administrator_login_password}
java -jar /home/azureuser/app3-usermgmt/usermgmt-webapp.war > /home/azureuser/app3-usermgmt/ums-start.log &
CUSTOM_DATA
}
Step-03-04: c7-03-web-linux-vmss-resource.tf - VMSS Resource¶
# Resource: Azure Linux Virtual Machine Scale Set - App1
resource "azurerm_linux_virtual_machine_scale_set" "web_vmss" {
# 1. Create VMSS only if Java App related DB Schema "webappdb" is created in MySQL Server
# 2. Only create VMSS if DB is ready with Virtual Network Rule so connection for Java App can be established to DB
depends_on = [azurerm_mysql_database.webappdb, azurerm_mysql_virtual_network_rule.mysql_virtual_network_rule]
name = "${local.resource_name_prefix}-web-vmss"
#computer_name_prefix = "vmss-app1" # if name argument is not valid one for VMs, we can use this for VM Names
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
sku = "Standard_DS1_v2"
instances = 2
admin_username = "azureuser"
admin_ssh_key {
username = "azureuser"
public_key = file("${path.module}/ssh-keys/terraform-azure.pub")
}
source_image_reference {
publisher = "RedHat"
offer = "RHEL"
sku = "83-gen2"
version = "latest"
}
os_disk {
storage_account_type = "Standard_LRS"
caching = "ReadWrite"
}
upgrade_mode = "Automatic"
network_interface {
name = "web-vmss-nic"
primary = true
network_security_group_id = azurerm_network_security_group.web_vmss_nsg.id
ip_configuration {
name = "internal"
primary = true
subnet_id = azurerm_subnet.websubnet.id
#load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.web_lb_backend_address_pool.id]
application_gateway_backend_address_pool_ids = [azurerm_application_gateway.web_ag.backend_address_pool[0].id]
}
}
#custom_data = filebase64("${path.module}/app-scripts/redhat-app1-script.sh")
custom_data = base64encode(local.webvm_custom_data)
}
Step-04: Application Gateway Configs - c9-02-application-gateway-resource.tf¶
- We will update the
backend_http_settingsblock - cookie_based_affinity = "Enabled"
- affinity_cookie_name = "ApplicationGatewayAffinity"
- port = 8080
- We will update the
probeblock - port = 8080
- path = "/login"
- body = "Username"
# Resource-2: Azure Application Gateway - Standard resource "azurerm_application_gateway" "web_ag" { name = "${local.resource_name_prefix}-web-ag" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location # START: --------------------------------------- # # SKU: Standard_v2 (New Version ) sku { name = "Standard_v2" tier = "Standard_v2" #capacity = 2 } autoscale_configuration { min_capacity = 0 max_capacity = 10 } # END: --------------------------------------- # gateway_ip_configuration { name = "my-gateway-ip-configuration" subnet_id = azurerm_subnet.agsubnet.id } # Frontend Port - HTTP Port 80 frontend_port { name = local.frontend_port_name_http port = 80 } # Frontend Port - HTTP Port 443 frontend_port { name = local.frontend_port_name_https port = 443 } # Frontend IP Configuration frontend_ip_configuration { name = local.frontend_ip_configuration_name public_ip_address_id = azurerm_public_ip.web_ag_publicip.id } # App1 Configs backend_address_pool { name = local.backend_address_pool_name_app1 } backend_http_settings { name = local.http_setting_name_app1 #cookie_based_affinity = "Disabled" cookie_based_affinity = "Enabled" affinity_cookie_name = "ApplicationGatewayAffinity" #path = "/app1/" port = 8080 protocol = "Http" request_timeout = 60 probe_name = local.probe_name_app1 } probe { name = local.probe_name_app1 host = "127.0.0.1" interval = 30 timeout = 30 unhealthy_threshold = 3 protocol = "Http" port = 8080 path = "/login" match { # Optional body = "Username" status_code = ["200"] } } # HTTP Listener - Port 80 http_listener { name = local.listener_name_http frontend_ip_configuration_name = local.frontend_ip_configuration_name frontend_port_name = local.frontend_port_name_http protocol = "Http" } # HTTP Routing Rule - HTTP to HTTPS Redirect request_routing_rule { name = local.request_routing_rule_name_http rule_type = "Basic" http_listener_name = local.listener_name_http redirect_configuration_name = local.redirect_configuration_name } # Redirect Config for HTTP to HTTPS Redirect redirect_configuration { name = local.redirect_configuration_name redirect_type = "Permanent" target_listener_name = local.listener_name_https include_path = true include_query_string = true } # SSL Certificate Block ssl_certificate { name = local.ssl_certificate_name password = "kalyan" data = filebase64("${path.module}/ssl-self-signed/httpd.pfx") } # HTTPS Listener - Port 443 http_listener { name = local.listener_name_https frontend_ip_configuration_name = local.frontend_ip_configuration_name frontend_port_name = local.frontend_port_name_https protocol = "Https" ssl_certificate_name = local.ssl_certificate_name custom_error_configuration { custom_error_page_url = "${azurerm_storage_account.storage_account.primary_web_endpoint}502.html" status_code = "HttpStatus502" } custom_error_configuration { custom_error_page_url = "${azurerm_storage_account.storage_account.primary_web_endpoint}403.html" status_code = "HttpStatus403" } } # HTTPS Routing Rule - Port 443 request_routing_rule { name = local.request_routing_rule_name_https rule_type = "Basic" http_listener_name = local.listener_name_https backend_address_pool_name = local.backend_address_pool_name_app1 backend_http_settings_name = local.http_setting_name_app1 } }
Step-05: Enable Bastion Host¶
Step-05-01: c8-02-bastion-host-linuxvm.tf¶
- Enable all configs from file
c8-02-bastion-host-linuxvm.tf
Step-05-02: c8-03-move-ssh-key-to-bastion-host.tf¶
- Enable all configs from file
c8-03-move-ssh-key-to-bastion-host.tf
Step-05-03: c8-05-bastion-outputs.tf¶
- Enable all configs from file
c8-05-bastion-outputs.tf
Step-06: TF Configs Untouched¶
- c1-versions.tf
- c2-generic-input-variables.tf
- c3-locals.tf
- c4-random-resources.tf
- c5-resource-group.tf
- c6-01-vnet-input-variables.tf
- c6-02-virtual-network.tf
- c6-04-app-subnet-and-nsg.tf
- c6-05-db-subnet-and-nsg.tf
- c6-06-bastion-subnet-and-nsg.tf
- c6-07-ag-subnet-and-nsg.tf
- c6-08-vnet-outputs.tf
- c7-02-web-linux-vmss-nsg-inline-basic.tf
- c7-04-web-linux-vmss-outputs.tf
- c7-05-web-linux-vmss-autoscaling-default-profile.tf
- c7-06-web-linux-vmss-autoscaling-default-and-recurrence-profiles.tf
- c7-07-web-linux-vmss-autoscaling-default-recurrence-fixed-profiles.tf
- c8-01-bastion-host-input-variables.tf
- c8-04-AzureBastionService.tf
- c9-01-application-gateway-input-variables.tf
- c9-03-application-gateway-outputs.tf
- c10-01-storage-account-input-variables.tf
- c10-02-storage-account.tf
- c10-03-storage-account-outputs.tf
Step-07: Execute Terraform Commands¶
# Terraform Initialize
terraform init
# Terraform Validate
terraform validate
# Terraform Plan
terraform plan -var-file=secrets.tfvars
# Terraform Apply
terraform apply -var-file=secrets.tfvars
Step-08: Connect to MySQL DB from Bastion Host VM and VMSS VM¶
- Test from Bastion Host which confirms our Azure MySQL firewall rule test
- Test from VMSS VM1 or VM2 confirms that our
Azure MySQL Virtual Network ruleandWeb Subnet Service Endpoint Configswe have enabled private communication fromWeb Subnet hosted VM's to Azure MySQL Single Server# SSH to Bastion Host ssh -i ssh-keys/terraform-azure.pem azureuser@<Bastion-Public-IP> sudo su - # Connect to MySQL DB mysql -h hr-dev-mysql.mysql.database.azure.com -u dbadmin@hr-dev-mysql -p # DB Password to use mysql_db_password = "H@Sh1CoR3!" # SSH to Web VMSS VM1 or VM2 ssh -i /tmp/terraform-azure.pem azureuser@<VMSS-VM1-Private-IP> ssh -i /tmp/terraform-azure.pem azureuser@10.1.1.6 # Connect to MySQL DB from Web VMSS VM1 or VM2 (This happens via Virtual Network we created from Web Subnet to MySQL Server) mysql -h hr-dev-mysql.mysql.database.azure.com -u dbadmin@hr-dev-mysql -p
Step-09: Verify VMSS VM1 or VM2 Custom Data installed Apps¶
- Verify
/var/log/cloud-init-output.log - Verify User Management Web Application (UMS App) startup log
/home/azureuser/app3-usermgmt/ums-start.log
# Verify VMSS VM1 or VM2 cloud-init-output.log
cd /var/log
tail -100f cloud-init-output.log
# Verify User Management UMS App in VMSS VM1 or VM2
cd /home/azureuser/app3-usermgmt
ls
tail -100f /home/azureuser/app3-usermgmt/ums-start.log
more /home/azureuser/app3-usermgmt/ums-start.log
Step-10: Verify Application Gateway Health¶
- Go to Services -> Application Gateways -> hr-dev-web-ag -> Monitoring -> Backend Health
- Backend Health
- Health Probes -> Test Probe
- Insights
Step-11: Verify by accessing Application¶
# Update Host Entry
sudo vi /etc/hosts
<APP-GW-PublicIP> terraformguru.com
# Custom Host Entries
20.85.193.158 terraformguru.com
# Access Application
http://terraformguru.com # Should redirect to https URL
https://terraformguru.com
Username: admin101
Password: password101
- Test List User
- Test Create User
- Test login with newly created user
# Important Notes
1. User Management Web Application (UMS Web App) is coded in such a way during the startup of the application, it will create a default admin user in MySQL Database connected to it.
2. If connection to MySQL Server fails when UMS Web App is starting, it will come online.
Step-12: Clean-up¶
# Destroy Resources
terraform destroy -var-file=secrets.tfvars -auto-approve
# Delete Files
rm -rf .terraform*
rm -rf terraform.tfstate*
Additional Reference¶
🎉 New Course
Ultimate DevOps Real-World Project Implementation on AWS
$15.99
$84.99
81% OFF
MARCH2026
Enroll Now on Udemy →
🎉 Offer