Skip to content

Instantly share code, notes, and snippets.

@mike-seger
Last active July 4, 2024 17:10
Show Gist options
  • Save mike-seger/7b433738875f087803c5e9dd0920accb to your computer and use it in GitHub Desktop.
Save mike-seger/7b433738875f087803c5e9dd0920accb to your computer and use it in GitHub Desktop.
2 - azure app + gw full playbook

Running the Playbook

  1. Install Ansible and Azure Collection:
pip install ansible
ansible-galaxy collection install azure.azcollection
# ansible-galaxy collection download azure.azcollection
  1. Set Up Azure Credentials:
    Configure your Azure credentials using az login or by setting up a service principal.

  2. Run the Playbook with the app_env_param parameter: Save the playbook to a file, e.g., deploy_webapps.yml, and run it using the ansible-playbook command. You can pass the app_env_param parameter as follows:

ansible-playbook deploy_webapps.yml --extra-vars "app_env_param=production"

This playbook will dynamically determine the spring_boot_apps variable based on the app_instances and gw_instances lists, deploy your Spring Boot applications as web apps on Azure, and configure the spring.profiles.active and app.env properties for each app. Adjust the variables and paths to suit your specific requirements.

---
- name: Deploy Spring Boot Applications and Configure Azure Resources
hosts: localhost
vars:
resource_group: resourcegroup1
location: eastus
app_plan_name: app-plan
gw_plan_name: gw-plan
service_plans:
- name: app-plan
- name: gw-plan
app_instances:
- testapp1
- testapp2
gw_instances:
- gateway
jar_directory: "/path/to/jars"
num_app_instances: 4 # Global variable for the number of instances per app
num_gw_instances: 1 # Number of gateway instances
app_prefix: "app"
gw_prefix: "gw"
address_prefixes: "10.0.0.0/16"
subnet_prefix: "10.0.1.0/24"
app_gateway_subnet_prefix: "10.0.2.0/24"
app_gateway_name: appGateway
vnet_name: appVNet
subnet_name: appSubnet
app_gateway_subnet_name: appGatewaySubnet
app_env: "{{ app_env_param }}" # Parameter for app.env, default value if not passed
tasks:
- name: Create resource group
command: >
az group create --name {{ resource_group }} --location {{ location }}
ignore_errors: yes
- name: Determine spring_boot_apps for app_instances
set_fact:
spring_boot_apps: "{{ spring_boot_apps | default([]) + [{'name': item, 'jar_path': jar_directory + '/' + item + '.jar', 'plan': app_plan_name}] }}"
with_items: "{{ app_instances }}"
- name: Determine spring_boot_apps for gw_instances
set_fact:
spring_boot_apps: "{{ spring_boot_apps + [{'name': item, 'jar_path': jar_directory + '/' + item + '.jar', 'plan': gw_plan_name}] }}"
with_items: "{{ gw_instances }}"
- name: Create service plans
command: >
az appservice plan create --name {{ item.name }} --resource-group {{ resource_group }} --location {{ location }} --sku F1 --is-linux
loop: "{{ service_plans }}"
- name: Create web apps
command: >
az webapp create --name {{ item.name }} --resource-group {{ resource_group }} --plan {{ item.plan }} --runtime "JAVA|11-java11"
with_items: "{{ spring_boot_apps }}"
- name: Deploy JAR files to Azure Web Apps
shell: >
az webapp deploy --resource-group {{ resource_group }} --name {{ item.name }} --src-path {{ item.jar_path }} --type jar
with_items: "{{ spring_boot_apps }}"
no_log: true
- name: Set spring.profiles.active and app.env properties
command: >
az webapp config appsettings set --resource-group {{ resource_group }} --name {{ item.name }} --settings spring.profiles.active=azure app.env={{ app_env }}
with_items: "{{ spring_boot_apps }}"
- name: Get App Plan ID
command: az appservice plan show --name {{ app_plan_name }} --resource-group {{ resource_group }} --query id --output tsv
register: app_plan_id
changed_when: false
- name: Get GW Plan ID
command: az appservice plan show --name {{ gw_plan_name }} --resource-group {{ resource_group }} --query id --output tsv
register: gw_plan_id
changed_when: false
- name: Create Virtual Network
command: >
az network vnet create --name {{ vnet_name }}
--resource-group {{ resource_group }}
--address-prefixes {{ address_prefixes }}
--subnet-name {{ subnet_name }}
--subnet-prefix {{ subnet_prefix }}
register: vnet
changed_when: false
- name: Create App Gateway Subnet
command: >
az network vnet subnet create --vnet-name {{ vnet_name }}
--name {{ app_gateway_subnet_name }}
--resource-group {{ resource_group }}
--address-prefix {{ app_gateway_subnet_prefix }}
register: app_gateway_subnet
changed_when: false
- name: Integrate App Instances with VNet
command: >
az webapp vnet-integration add --name {{ item.0 }}-{{ item.1 }}
--resource-group {{ resource_group }}
--vnet {{ vnet_name }}
--subnet {{ subnet_name }}
--vnet-resource-group {{ net_resource_group }}
with_nested:
- "{{ app_instances }}"
- "{{ range(1, num_app_instances + 1) | list }}"
- name: Integrate GW Instances with VNet
command: >
az webapp vnet-integration add --name {{ gw_prefix }}-{{ item }}
--resource-group {{ resource_group }}
--vnet {{ vnet_name }}
--subnet {{ subnet_name }}
--vnet-resource-group {{ net_resource_group }}
loop: "{{ range(1, num_gw_instances + 1) | list }}"
- name: Create Private Endpoints for App Instances
command: >
az network private-endpoint create --name {{ item.0 }}-{{ item.1 }}PrivateEndpoint
--resource-group {{ resource_group }}
--vnet-name {{ vnet_name }}
--subnet {{ subnet_name }}
--vnet-resource-group {{ net_resource_group }}
--private-connection-resource-id $(az webapp show --name {{ item.0 }}-{{ item.1 }} --resource-group {{ resource_group }} --query id --output tsv)
--group-ids sites
--connection-name {{ item.0 }}-{{ item.1 }}Connection
with_nested:
- "{{ app_instances }}"
- "{{ range(1, num_app_instances + 1) | list }}"
- name: Determine Private IP Addresses
command: >
az network private-endpoint show --name {{ item.0 }}-{{ item.1 }}PrivateEndpoint
--resource-group {{ resource_group }}
--query 'customDnsConfigurations[0].ipAddresses[0]'
--output tsv
register: private_ips
with_nested:
- "{{ app_instances }}"
- "{{ range(1, num_app_instances + 1) | list }}"
changed_when: false
- name: Configure Private DNS Zones
command: az network private-dns zone create --resource-group {{ resource_group }} --name privatelink.azurewebsites.net
- name: Link VNet to Private DNS Zone
command: >
az network private-dns link vnet create --resource-group {{ resource_group }}
--zone-name privatelink.azurewebsites.net
--name appVNetLink
--virtual-network {{ vnet_name }}
--registration-enabled false
- name: Add DNS Records for App Instances
command: >
az network private-dns record-set a add-record --resource-group {{ resource_group }}
--zone-name privatelink.azurewebsites.net
--record-set-name {{ item.0.0 }}-{{ item.0.1 }}
--ipv4-address {{ item.1 }}
with_nested:
- "{{ private_ips.results | map(attribute='item') | list }}"
- "{{ private_ips.results | map(attribute='stdout') | list }}"
- name: Restrict Public Access to App Instances
command: >
az webapp config access-restriction add --name {{ item.0 }}-{{ item.1 }}
--resource-group {{ resource_group }}
--rule-name DenyPublicAccess
--priority 100
--action Deny
--ip-address 0.0.0.0/0
with_nested:
- "{{ app_instances }}"
- "{{ range(1, num_app_instances + 1) | list }}"
- name: List all webapps in resource group
command: az webapp list --resource-group {{ resource_group }} --query [].name --output tsv
register: all_webapps
changed_when: false
- name: Remove Web Apps not in app_instances
command: az webapp delete --resource-group {{ resource_group }} --name {{ item }}
when: "'{{ item }}' not in (app_instances + gw_instances)"
loop: "{{ all_webapps.stdout_lines }}"
- name: List all private endpoints in resource group
command: az network private-endpoint list --resource-group {{ resource_group }} --query [].name --output tsv
register: all_private_endpoints
changed_when: false
- name: Remove Private Endpoints not in app_instances
command: az network private-endpoint delete --resource-group {{ resource_group }} --name {{ item }}
when: "'{{ item }}PrivateEndpoint' not in (app_instances | map('join', 'PrivateEndpoint'))"
loop: "{{ all_private_endpoints.stdout_lines }}"
- name: List all DNS records in private DNS zone
command: az network private-dns record-set list --resource-group {{ resource_group }} --zone-name privatelink.azurewebsites.net --query [].name --output tsv
register: all_dns_records
changed_when: false
- name: Remove DNS Records not in app_instances
command: >
az network private-dns record-set a remove-record --resource-group {{ resource_group }}
--zone-name privatelink.azurewebsites.net
--record-set-name {{ item }}
--ipv4-address $(az network private-endpoint show --name {{ item }}PrivateEndpoint --resource-group {{ resource_group }} --query 'customDnsConfigurations[0].ipAddresses[0]' --output tsv)
when: "'{{ item }}.privatelink.azurewebsites.net' not in (app_instances | map('join', '.privatelink.azurewebsites.net'))"
loop: "{{ all_dns_records.stdout_lines }}"
---
- name: Deploy Spring Boot Applications and Configure Azure Resources
hosts: localhost
vars:
resource_group: resourcegroup1
location: eastus
app_plan_name: app-plan
gw_plan_name: gw-plan
app_instances:
- testapp1
- testapp2
gw_instances:
- gateway
jar_directory: "/path/to/jars"
num_app_instances: 4 # Global variable for the number of instances per app
num_gw_instances: 1 # Number of gateway instances
app_prefix: "app"
gw_prefix: "gw"
address_prefixes: "10.0.0.0/16"
subnet_prefix: "10.0.1.0/24"
app_gateway_subnet_prefix: "10.0.2.0/24"
app_gateway_name: appGateway
vnet_name: appVNet
subnet_name: appSubnet
app_gateway_subnet_name: appGatewaySubnet
app_env: "{{ app_env_param }}" # Parameter for app.env, default value if not passed
tasks:
- name: Create resource group
azure.azcollection.azure_rm_resourcegroup:
name: "{{ resource_group }}"
location: "{{ location }}"
ignore_errors: yes
- name: Determine spring_boot_apps for app_instances
set_fact:
spring_boot_apps: "{{ spring_boot_apps | default([]) + [{'name': item, 'jar_path': lookup('file', jar_directory + '/' + item + '.jar'), 'plan': app_plan_name}] }}"
with_items: "{{ app_instances }}"
- name: Determine spring_boot_apps for gw_instances
set_fact:
spring_boot_apps: "{{ spring_boot_apps + [{'name': item, 'jar_path': lookup('file', jar_directory + '/' + item + '.jar'), 'plan': gw_plan_name}] }}"
with_items: "{{ gw_instances }}"
- name: Create service plans
azure.azcollection.azure_rm_appserviceplan:
resource_group: "{{ resource_group }}"
name: "{{ item.plan }}"
location: "{{ location }}"
sku: F1
is_linux: true
loop:
- "{{ app_plan_name }}"
- "{{ gw_plan_name }}"
- name: Create web apps
azure.azcollection.azure_rm_webapp:
resource_group: "{{ resource_group }}"
name: "{{ item.name }}"
plan: "{{ item.plan }}"
location: "{{ location }}"
state: present
linux_fx_version: "JAVA|11-java11"
with_items: "{{ spring_boot_apps }}"
- name: Deploy JAR files to Azure Web Apps
command: |
az webapp deploy --resource-group {{ resource_group }} --name {{ item.name }} --src-path {{ item.jar_path }} --type jar
with_items: "{{ spring_boot_apps }}"
- name: Set spring.profiles.active and app.env properties
azure.azcollection.azure_rm_webapp_config:
resource_group: "{{ resource_group }}"
name: "{{ item.name }}"
app_settings:
- name: spring.profiles.active
value: "azure"
- name: app.env
value: "{{ app_env }}"
with_items: "{{ spring_boot_apps }}"
- name: Get App Plan ID
command: az appservice plan show --name {{ app_plan_name }} --resource-group {{ resource_group }} --query id --output tsv
register: app_plan_id
changed_when: false
- name: Get GW Plan ID
command: az appservice plan show --name {{ gw_plan_name }} --resource-group {{ resource_group }} --query id --output tsv
register: gw_plan_id
changed_when: false
- name: Create Virtual Network
azure.azcollection.azure_rm_virtualnetwork:
name: "{{ vnet_name }}"
resource_group: "{{ resource_group }}"
address_prefixes: "{{ address_prefixes }}"
location: "{{ location }}"
subnets:
- name: "{{ subnet_name }}"
address_prefix: "{{ subnet_prefix }}"
- name: "{{ app_gateway_subnet_name }}"
address_prefix: "{{ app_gateway_subnet_prefix }}"
- name: Create and Move App Instances
azure.azcollection.azure_rm_webapp:
resource_group: "{{ resource_group }}"
name: "{{ item.app_name }}-{{ item.instance }}"
plan: "{{ app_plan_id.stdout }}"
location: "{{ location }}"
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
- name: Create and Move GW Instances
azure.azcollection.azure_rm_webapp:
resource_group: "{{ resource_group }}"
name: "{{ gw_prefix }}-{{ item }}"
plan: "{{ gw_plan_id.stdout }}"
location: "{{ location }}"
with_sequence: start=1 end={{ num_gw_instances }}
- name: Integrate App Instances with VNet
azure.azcollection.azure_rm_webapp_vnet_integration:
resource_group: "{{ resource_group }}"
name: "{{ item.app_name }}-{{ item.instance }}"
vnet: "{{ vnet_name }}"
subnet: "{{ subnet_name }}"
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
- name: Integrate GW Instances with VNet
azure.azcollection.azure_rm_webapp_vnet_integration:
resource_group: "{{ resource_group }}"
name: "{{ gw_prefix }}-{{ item }}"
vnet: "{{ vnet_name }}"
subnet: "{{ subnet_name }}"
with_sequence: start=1 end={{ num_gw_instances }}
- name: Create Private Endpoints for App Instances
azure.azcollection.azure_rm_private_endpoint:
resource_group: "{{ resource_group }}"
name: "{{ item.app_name }}-{{ item.instance }}PrivateEndpoint"
vnet_name: "{{ vnet_name }}"
subnet_name: "{{ subnet_name }}"
private_connection_resource_id: "{{ lookup('azure.azcollection.azure_rm_webapp', 'name=' + item.app_name + '-' + item.instance + ' resource_group=' + resource_group).id }}"
group_ids: "sites"
connection_name: "{{ item.app_name }}-{{ item.instance }}Connection"
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
- name: Determine Private IP Addresses
command: az network private-endpoint show --name "{{ item.app_name }}-{{ item.instance }}PrivateEndpoint" --resource-group "{{ resource_group }}" --query 'customDnsConfigurations[0].ipAddresses[0]' --output tsv
register: private_ips
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
changed_when: false
- name: Configure Private DNS Zones
azure.azcollection.azure_rm_private_dnszone:
resource_group: "{{ resource_group }}"
name: privatelink.azurewebsites.net
- name: Link VNet to Private DNS Zone
azure.azcollection.azure_rm_private_dnszone_virtualnetwork_link:
resource_group: "{{ resource_group }}"
zone_name: privatelink.azurewebsites.net
name: appVNetLink
vnet_name: "{{ vnet_name }}"
registration_enabled: false
- name: Add DNS Records for App Instances
azure.azcollection.azure_rm_private_dnsrecord:
resource_group: "{{ resource_group }}"
zone_name: privatelink.azurewebsites.net
record_type: A
name: "{{ item.item.app_name }}-{{ item.item.instance }}"
ttl: 300
value: "{{ item.stdout }}"
with_items: "{{ private_ips.results }}"
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
- name: Restrict Public Access to App Instances
azure.azcollection.azure_rm_webapp_access_restriction:
resource_group: "{{ resource_group }}"
name: "{{ item.app_name }}-{{ item.instance }}"
rule_name: "DenyPublicAccess"
priority: 100
action: Deny
ip_address: "0.0.0.0/0"
with_subelements:
- "{{ app_instances | map('product', with_sequence('start=1 end=' + num_app_instances|string)) | list }}"
- list
- name: Remove Web App
azure.azcollection.azure_rm_webapp:
resource_group: "{{ resource_group }}"
name: "{{ item }}"
state: absent
when: "'{{ item }}' not in app_instances"
loop: "{{ query('azure.azcollection.azure_rm_webapp', 'resource_group=' + resource_group) | map(attribute='name') }}"
- name: Remove Private Endpoints
azure.azcollection.azure_rm_private_endpoint:
resource_group: "{{ resource_group }}"
name: "{{ item }}PrivateEndpoint"
state: absent
when: "'{{ item }}' not in app_instances"
loop: "{{ query('azure.azcollection.azure_rm_webapp', 'resource_group=' + resource_group) | map(attribute='name') }}"
- name: Remove DNS Records
azure.azcollection.azure_rm_private_dnsrecord:
resource_group: "{{ resource_group }}"
zone_name: privatelink.azurewebsites.net
name: "{{ item }}.privatelink.azurewebsites.net"
state: absent
when: "'{{ item }}' not in app_instances"
loop: "{{ query('azure.azcollection.azure_rm_webapp', 'resource_group=' + resource_group) | map(attribute='name') }}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment