Skip to main content

[AZ-104] Grand Labs - NovaTech Solutions

Cumulative Real-World Deployment Environment​


Central Narrative​

Company: NovaTech Solutions | Industry: Consulting and software development for the financial sector | Headquarters: Belo Horizonte, Brazil

NovaTech Solutions is a Brazilian software consulting company based in Belo Horizonte that serves regional banks and brokerage firms. With accelerated growth over the past two years, the company has decided to migrate its entire infrastructure to Azure, moving away from a legacy on-premises environment and a smaller cloud provider.

The migration requires deployment of a complete platform: centralized identity and governance, secure storage for financial data, scalable compute for client applications, isolated and monitored networking, and a robust business continuity plan. Each step of this lab represents a real phase of this migration, executed by NovaTech's cloud engineering team.

Resources will be distributed between the East US and Brazil South regions, respecting data residency requirements for financial clients. The prefix for all resources is nvt. Capture IDs, IPs, and object names produced in each step β€” they will be required in subsequent steps.


Prerequisites​

Install before starting

Validate all tools below before beginning Step 1.

ToolMinimum VersionRequired in StepsVerification
Azure CLI2.50+Allaz --version
Bicep CLIAny31–32az bicep install && az bicep version
AzCopyv10+23, 30azcopy --version
Docker Desktop24+38–39docker --version
curlAny40curl --version
Cost and time estimate
  • πŸ’° Estimated cost for complete execution: USD 15–30
  • ⏱️ Total estimated time: 12–16 hours distributed across multiple sessions
  • πŸ”‹ Stop and deallocate VMs, VMSS, and Bastion when ending each session
  • πŸ“ Primary region: East US | DR region: Brazil South
ResourceEstimated costNote
Azure Bastion (Standard)~USD 0.19/hStop after use
VM Standard_B2s~USD 0.08/hDeallocate between sessions
VMSS (2x B1s)~USD 0.04/hScale to 0 instances between sessions
App Service P1v3~USD 0.16/hScale down to F1 between sessions
Site Recovery~USD 0.16/VM/hDisable after Step 68
Load Balancer Standard~USD 0.008/hLow cost, can maintain

Naming Convention​

Resource TypeExample
Resource groupnvt-rg-main
Virtual networknvt-vnet-main
VMnvt-vm-app01
Storage accountnvtprodsa<suffix> (no hyphens, max 24 chars)
NSGnvt-nsg-compute
Key Vaultnvt-kv-prod
Container Registrynvtregistry<suffix>
Unique suffixes

Resources like storage accounts and container registries require globally unique names. Use a 4–6 character suffix (e.g., your initials + number) and maintain the same suffix throughout the entire lab.


Index by Exam Domain​

DomainSteps
Domain 1 β€” Identity and Governance1–13
Domain 2 β€” Storage14–30
Domain 3 β€” Infrastructure as Code31–32
Domain 4 β€” Compute and Containers33–41
Domain 5 β€” App Service42–47
Domain 6 β€” Networking48–58
Domain 7 β€” Monitoring59–64
Domain 8 β€” Backup and DR65–69

Domain 1 β€” Identity and Governance​

Step 1 β€” Create users and groups in Microsoft Entra ID​

Context: NovaTech needs to structure team identities before any Azure resources are provisioned. The cloud engineering team and financial administrators need separate identities, organized into groups that reflect their responsibilities. This is the foundation for all governance that will follow in subsequent steps.

Tasks:

[CLI] Create two member users in Microsoft Entra ID using az ad user create:

Display NameUser Principal NameEnabled
NVT Cloud Engineernvt-cloud-eng@<your-domain>.onmicrosoft.comtrue
NVT Finance Adminnvt-finance-admin@<your-domain>.onmicrosoft.comtrue

For each user, set a temporary password and accountEnabled: true.

[CLI] Create two security groups using az ad group create:

NameTypeMembership Type
nvt-cloud-engineersSecurityAssigned
nvt-finance-adminsSecurityAssigned

[CLI] Add each user to the corresponding group using az ad group member add:

  • nvt-cloud-eng β†’ nvt-cloud-engineers
  • nvt-finance-admin β†’ nvt-finance-admins

[CLI] Capture and store the Object ID of each group using az ad group show --query id. These values will be used in role assignments in subsequent steps.

Validation Criteria:

# Should return 2 objects with Enabled = true:
az ad user list --filter "startswith(displayName,'NVT')" \
--query "[].{Name:displayName,UPN:userPrincipalName,Enabled:accountEnabled}"

# Each command should return an array with a single name:
az ad group member list --group nvt-cloud-engineers --query "[].displayName"
az ad group member list --group nvt-finance-admins --query "[].displayName"

Step 2 β€” Manage user and group properties​

Context: With identities created, the HR team requested that user profiles reflect the organizational structure: department, job title, and work location. Additionally, the financial administrators group needs a clear description for regulatory compliance purposes.

Tasks:

[CLI] Update properties for each user using az ad user update:

UserjobTitledepartmentusageLocation
nvt-cloud-engCloud EngineerTechnologyBR
nvt-finance-adminFinance AdministratorFinanceBR

[CLI] Update the description of the nvt-finance-admins group to Financial administrators group with access to regulated data resources using az ad group update --description.

Validation Criteria:

# Should return Title = Cloud Engineer, Dept = Technology, Location = BR:
az ad user show --id nvt-cloud-eng@<domain> \
--query "{Title:jobTitle,Dept:department,Location:usageLocation}"

# Should return the defined description string:
az ad group show --group nvt-finance-admins --query "description"

Step 3 β€” Manage licenses in Microsoft Entra ID​

Context: NovaTech has acquired Microsoft 365 licenses for team members. The nvt-cloud-eng user needs an assigned license to access associated services. The usageLocation defined in Step 2 is a mandatory prerequisite for this assignment.

Tasks:

[Portal] Navigate to: Microsoft Entra ID β†’ Users β†’ nvt-cloud-eng β†’ Licenses β†’ Assignments. Assign an available license in the tenant (e.g., Microsoft Entra ID P2 or Microsoft 365 trial). Confirm that the usageLocation field is populated before saving.

[CLI] Verify licenses assigned to user nvt-cloud-eng:

az ad user show --id nvt-cloud-eng@<domain> --query "assignedLicenses"

Validation Criteria:

# Should return array with at least 1 object with non-null skuId:
az ad user show --id nvt-cloud-eng@<domain> --query "assignedLicenses"

Step 4 β€” Manage external users​

Context: An external auditor from a regulatory firm needs temporary access to NovaTech's Azure environment to review compliance controls. Security policy requires external users to be treated as guests with limited access, never as tenant members.

Tasks:

[CLI] Send a collaboration invite to an external email address using az ad invitation create:

ParameterValue
--invited-user-display-nameNVT External Auditor
--invite-redirect-urlhttps://portal.azure.com
--invited-user-email-address<external test email>

[CLI] List guest users in the tenant and capture the external auditor's Object ID:

az ad user list --filter "userType eq 'Guest'" \
--query "[?displayName=='NVT External Auditor'].id"

Validation Criteria:

# Should return at least 1 object with Type = Guest and Name = NVT External Auditor:
az ad user list --filter "userType eq 'Guest'" \
--query "[].{Name:displayName,Type:userType}"

Step 5 β€” Configure Self-Service Password Reset (SSPR)​

Context: NovaTech's helpdesk is overwhelmed with password reset requests. Leadership has decided to enable SSPR for the nvt-cloud-engineers group, allowing engineers to reset their own passwords without support intervention.

Tasks:

[Portal] Enable SSPR for the nvt-cloud-engineers group at: Microsoft Entra ID β†’ Protection β†’ Password reset β†’ Properties. Change scope from None to Selected and select the group.

[Portal] Configure authentication methods at: Microsoft Entra ID β†’ Protection β†’ Password reset β†’ Authentication methods

ParameterValue
Number of methods required to reset1
Methods availableEmail, Mobile phone

[Portal] Configure registration at: Microsoft Entra ID β†’ Protection β†’ Password reset β†’ Registration

ParameterValue
Require users to register when signing inYes
Days before users are asked to re-confirm180

Validation Criteria:

tip
  • Password reset β†’ Properties: scope displays Selected with nvt-cloud-engineers listed.
  • Password reset β†’ Authentication methods: number of methods = 1 and at least two methods enabled.

Step 6 β€” Manage Azure built-in roles​

Context: With identities organized, it's time to grant access to Azure resources. The nvt-cloud-engineers group will need access to create and manage resources, while the nvt-finance-admins group should only view costs. Assignments must follow the principle of least privilege.

Tasks:

[CLI] Create the main resource group nvt-rg-main in the East US region using az group create. This group will be the primary container for all lab resources.

[CLI] Assign the Contributor role to the nvt-cloud-engineers group in the scope of resource group nvt-rg-main using az role assignment create. Use the group Object ID captured in Step 1.

[CLI] Assign the Cost Management Reader role to the nvt-finance-admins group in the current subscription scope. Capture the subscription ID with az account show --query id.

Validation Criteria:

# Should return object with Role = Contributor:
az role assignment list --resource-group nvt-rg-main \
--query "[?principalName=='nvt-cloud-engineers'].{Role:roleDefinitionName,Scope:scope}"

# Should return object with Role = Cost Management Reader:
az role assignment list --scope /subscriptions/<id> \
--query "[?contains(roleDefinitionName,'Cost Management')].{Role:roleDefinitionName}"

Step 7 β€” Assign roles at different scopes and interpret assignments​

Context: NovaTech has a second staging environment for testing financial applications. It's necessary to create a staging resource group and assign specific permissions only for that environment, demonstrating how Azure RBAC works across multiple scopes.

Tasks:

[CLI] Create the resource group nvt-rg-staging in the Brazil South region using az group create.

[CLI] Assign the Reader role to user nvt-cloud-eng with scope only on resource group nvt-rg-staging using az role assignment create with the user's Object ID.

[CLI] List all effective role assignments for user nvt-cloud-eng and identify the scope difference between the two resource groups:

az role assignment list --assignee <object-id> --all

Validation Criteria:

# Should return at least 2 entries with distinct scopes:
az role assignment list --assignee <nvt-cloud-eng-object-id> --all \
--query "[].{Role:roleDefinitionName,Scope:scope}"

Step 8 β€” Implement and manage Azure Policy​

Context: The compliance team identified that resources without tags are being created in an uncontrolled manner, making cost allocation per client difficult. A governance policy must require all resources in the production resource group to have the CostCenter tag.

Tasks:

[CLI] Assign the built-in Azure Policy Require a tag on resources (ID: 871b6d14-10aa-478d-b590-94f262ecfa99) in the scope of resource group nvt-rg-main using az policy assignment create:

ParameterValue
--namenvt-require-costcenter
--policy871b6d14-10aa-478d-b590-94f262ecfa99
--scopeResource ID of nvt-rg-main
--params{"tagName": {"value": "CostCenter"}}

[CLI] Verify the policy assignment status and confirm that enforcementMode is set to Default:

az policy assignment show --name "nvt-require-costcenter"

Validation Criteria:

# Should return EnfMode = Default and Scope containing nvt-rg-main:
az policy assignment show --name "nvt-require-costcenter" \
--query "{Name:name,Scope:scope,EnfMode:enforcementMode}"

Step 9 β€” Configure resource locks​

Context: NovaTech's production resource group cannot be accidentally deleted. Asset protection policy defines that resource group nvt-rg-main must have a deletion lock before any production resources are deployed.

Tasks:

[CLI] Apply a CanNotDelete type lock to resource group nvt-rg-main using az lock create:

ParameterValue
--namenvt-lock-prod
--resource-groupnvt-rg-main
--lock-typeCanNotDelete
--notesNovaTech production protection

[CLI] Try to delete resource group nvt-rg-main with az group delete --name nvt-rg-main --yes and observe the returned error. Do not confirm deletion if it's not automatically blocked.

Validation Criteria:

# Should return Name = nvt-lock-prod, Type = CanNotDelete:
az lock list --resource-group nvt-rg-main \
--query "[].{Name:name,Type:level}"

The deletion attempt should fail with ScopeLocked code


---

### Step 10 β€” Apply and manage tags on resources

**Context:** NovaTech needs to track costs per client and per environment. All lab resources should receive standardized tags that allow filtering cost reports by `Environment`, `CostCenter` and `Owner`.

**Tasks:**

**[CLI]** Apply the tags below to the resource group `nvt-rg-main` using `az tag update --operation Merge`. Capture the RG resource ID with `az group show --query id`:

| Tag | Value |
|---|---|
| Environment | Production |
| CostCenter | NVT-CORE |
| Owner | cloud-team |

**[CLI]** Apply tags to the resource group `nvt-rg-staging`:

| Tag | Value |
|---|---|
| Environment | Staging |
| CostCenter | NVT-CORE |
| Owner | cloud-team |

**[CLI]** List all resource groups that have the tag `CostCenter=NVT-CORE`:

```bash
az group list --tag CostCenter=NVT-CORE

Validation Criteria:

# Should return 2 objects, one with Env = Production and another with Env = Staging:
az group list --tag CostCenter=NVT-CORE \
--query "[].{Name:name,Env:tags.Environment}"

Step 11 β€” Manage resource groups​

Context: NovaTech decided to create a third resource group dedicated exclusively to network resources shared between production and staging. Existing resources need to be moved as the architecture reorganization progresses.

Tasks:

[CLI] Create the resource group nvt-rg-network in the East US region with tags Environment: Shared and CostCenter: NVT-CORE using az group create --tags.

[CLI] Execute the move validation command (dry run) without implementing the actual move to verify the operation feasibility:

az resource invoke-action --action validateMoveResources

Validation Criteria:

# Should return Cost = NVT-CORE, Env = Shared, Location = eastus:
az group show --name nvt-rg-network \
--query "{Name:name,Env:tags.Environment,Cost:tags.CostCenter,Location:location}"

Step 12 β€” Manage subscriptions and management groups​

Context: NovaTech anticipates growth and will need to organize future subscriptions by business unit. The architecture team must configure management groups to prepare this hierarchical governance structure before new subscriptions are added.

Tasks:

[CLI] Create the management group nvt-mg-root with display name NovaTech Root using az account management-group create.

[CLI] Create the child management group nvt-mg-technology with display name NovaTech Technology subordinated to nvt-mg-root using the parameter --parent nvt-mg-root.

[CLI] Display the management groups hierarchy to confirm the parent-child structure:

az account management-group show --name nvt-mg-root --expand --recurse

Validation Criteria:

# Should return Children = ["nvt-mg-technology"], Parent = nvt-mg-root:
az account management-group show --name nvt-mg-root --expand --recurse \
--query "{Parent:name,Children:children[].name}"

Step 13 β€” Manage costs with alerts, budgets and Azure Advisor​

Context: NovaTech CFO defined a maximum monthly budget for the lab subscription. The team must configure a budget with automatic alerts to avoid unexpected expenses.

Tasks:

[Portal] Create the budget at: Cost Management + Billing β†’ Budgets

ParameterValue
Namenvt-monthly-budget
Reset periodMonthly
Amount100 USD
Alert conditionActual cost at 80%
Alert emailnvt-finance-admin@<domain>.onmicrosoft.com

[Portal] Register at least one cost recommendation at: Azure Advisor β†’ Cost. If there are no recommendations, register that the tab displays No recommendations.

Validation Criteria:

tip
  • Budget nvt-monthly-budget appears in the list with status Active and value 100 USD.
  • At least one alert configured with threshold 80 and type Actual.

Domain 2 β€” Storage​

Step 14 β€” Configure Azure Storage firewalls and virtual networks​

Context: NovaTech will store sensitive financial documents in Azure Storage. By security policy, the production storage account should reject public connections by default and accept only connections originating from the company's virtual network.

Tasks:

[CLI] Create the VNet nvt-vnet-main in resource group nvt-rg-network, region East US, with address space 10.10.0.0/16 using az network vnet create.

[CLI] Create the subnet nvt-snet-storage with prefix 10.10.1.0/24 inside the VNet. Enable the service endpoint for Microsoft.Storage with the parameter --service-endpoints Microsoft.Storage.

[CLI] Create the storage account nvtprodsa<suffix> in resource group nvt-rg-main, region East US, SKU Standard_LRS, with --default-action Deny and --bypass AzureServices using az storage account create.

[CLI] Add a VNet rule to the storage account to allow traffic from subnet nvt-snet-storage using az storage account network-rule add --vnet-name nvt-vnet-main --subnet nvt-snet-storage.

Validation Criteria:

# Should return "Deny":
az storage account show --name <sa-name> --resource-group nvt-rg-main \
--query "networkRuleSet.defaultAction"

# Should return array with the subnet Resource ID:
az storage account network-rule list --account-name <sa-name> --resource-group nvt-rg-main \
--query "virtualNetworkRules[].virtualNetworkResourceId"

Step 15 β€” Create and use SAS tokens​

Context: NovaTech needs to share financial reports with external partners without exposing the storage account access keys. Partners should have read access to a specific container for a limited period of 24 hours.

Tasks:

[CLI] Create the container nvt-reports in storage account nvtprodsa<suffix> with private access using az storage container create --public-access off.

[CLI] Generate an account-level SAS token with rl permissions (read, list) for service b (blob), valid for 24 hours, using az storage account generate-sas:

ParameterValue
--permissionsrl
--servicesb
--resource-typesco
--expiryISO 8601 date/time in +24h

[CLI] Use the generated SAS token to list the contents of container nvt-reports via az storage blob list --sas-token. Do not use the account key.

Validation Criteria:

# Should return without authentication error (empty container is acceptable):
az storage blob list --container-name nvt-reports \
--account-name <sa-name> --sas-token "<token>" --output table

Step 16 β€” Configure stored access policies​

Context: NovaTech security team wants to centralize SAS tokens control. Instead of ad hoc tokens, access to the nvt-reports container should be governed by stored access policies, allowing immediate revocation when necessary.

Tasks:

[CLI] Create the stored access policy nvt-read-policy in container nvt-reports with rl permissions and expiration in 7 days using az storage container policy create:

ParameterValue
--namenvt-read-policy
--container-namenvt-reports
--permissionsrl
--expiryISO 8601 date/time in +7d

[CLI] Generate a SAS token associated with the stored access policy using az storage container generate-sas --policy-name nvt-read-policy.

Validation Criteria:

# Should return object with nvt-read-policy listed and permissions = rl:
az storage container policy list \
--container-name nvt-reports --account-name <sa-name>

Step 17 β€” Manage access keys​

Context: NovaTech security policy requires periodic rotation of production storage account access keys. The team must rotate the secondary key and verify that access continues working with the primary key.

Tasks:

[CLI] List the storage account access keys and register the current value of key2:

az storage account keys list --account-name <sa-name> \
--query "[].{KeyName:keyName,Value:value}"

[CLI] Regenerate the storage account key2 using az storage account keys renew --key key2.

[CLI] List the keys again and compare the new key2 value with the one registered before rotation.

Validation Criteria:

tip

After regeneration, the key2 value should be different from the value registered before rotation. Execute az storage account keys list before and after and compare the key2 values.


Step 18 β€” Configure identity-based access for Azure Files​

Context: NovaTech will use Azure Files to share files between application servers. Access should be controlled via Microsoft Entra Kerberos, without depending on access keys.

Tasks:

[CLI] Enable Microsoft Entra Kerberos authentication in the production storage account for Azure Files:

az storage account update --enable-files-aadkerb true \
--name <sa-name> --resource-group nvt-rg-main

[Portal] Confirm the configuration at: Storage accounts β†’ <sa-name> β†’ File shares β†’ Active Directory. The authentication method should display Microsoft Entra Kerberos as Configured.

Validation Criteria:

# Should return directoryServiceOptions = AADKERB:
az storage account show --name <sa-name> \
--query "azureFilesIdentityBasedAuthentication"

Step 19 β€” Create and configure storage accounts​

Context: In addition to the production storage account, NovaTech needs a separate storage account for the staging environment in Brazil South. This account should use geo-redundancy to ensure durability of Brazilian customers' data.

Tasks:

[CLI] Create the storage account nvtstagingsa<suffix> in resource group nvt-rg-staging, region Brazil South, with the parameters:

ParameterValue
--skuStandard_GRS
--kindStorageV2
--locationbrazilsouth
--access-tierCool
--https-onlytrue

Validation Criteria:

# Should return HTTPS = true, Location = brazilsouth, SKU = Standard_GRS, Tier = Cool:
az storage account show --name <staging-sa-name> \
--query "{SKU:sku.name,Tier:accessTier,HTTPS:enableHttpsTrafficOnly,Location:primaryLocation}"

Step 20 β€” Configure Azure Storage redundancy​

Context: The production storage account was created with LRS, but the financial regulator requires critical data to have at least zone-redundant replication. The team must change the redundancy.

Tasks:

[CLI] Change the production storage account redundancy from Standard_LRS to Standard_ZRS using az storage account update --sku Standard_ZRS.

[CLI] Confirm the SKU change:

az storage account show --name <sa-name> --query "sku.name"

Validation Criteria:

# Should return "Standard_ZRS":
az storage account show --name <sa-name> --query "sku.name"

Step 21 β€” Configure object replication​

Context: To meet financial customers' business continuity requirements, blobs from the nvt-reports container in production (East US) should be automatically replicated to the staging storage account in Brazil South.

Tasks:

[CLI] Enable blob versioning in both storage accounts β€” mandatory prerequisite for object replication. Use az storage account blob-service-properties update --enable-versioning true for each one.

[CLI] Create the destination container nvt-reports-replica in the staging storage account using az storage container create.

[CLI] Create the object replication policy mapping nvt-reports (source) to nvt-reports-replica (destination):

az storage account or-policy create \
--source-account <prod-sa> \
--destination-account <staging-sa> \
--source-container nvt-reports \
--destination-container nvt-reports-replica

Validation Criteria:

# Should return at least 1 object with PolicyId populated:
az storage account or-policy list --account-name <prod-sa> \
--query "[].{PolicyId:policyId,Dest:destinationAccount}"

Step 22 β€” Configure storage account encryption​

Context: The security audit requires that production storage account data be encrypted with a customer-managed key (CMK) stored in a dedicated Azure Key Vault.

Tasks:

[CLI] Create the Azure Key Vault nvt-kv-prod in resource group nvt-rg-main, region East US, with --enable-soft-delete true and --enable-purge-protection true using az keyvault create.

[CLI] Enable system-assigned managed identity in the production storage account:

az storage account update --name <sa-name> --assign-identity

[CLI] Grant the storage account managed identity the Key Vault Crypto Service Encryption User permission in the Key Vault. The principal ID is the storage account's identity.principalId.

[CLI] Create the key nvt-storage-key in the Key Vault and configure the storage account to use CMK:

az keyvault key create --name nvt-storage-key --vault-name nvt-kv-prod --kty RSA --size 2048

az storage account update --name <sa-name> \
--encryption-key-source Microsoft.Keyvault \
--encryption-key-vault <vault-uri> \
--encryption-key-name nvt-storage-key

Validation Criteria:

# Should return "Microsoft.Keyvault":
az storage account show --name <sa-name> --query "encryption.keySource"

Step 23 β€” Manage data with Azure Storage Explorer and AzCopy​

Context: The IT team needs to upload a set of report files to the nvt-reports container in production using AzCopy, which is the company's standard tool for batch transfers.

Prerequisite

AzCopy v10+ installed locally. Verify with: azcopy --version

Tasks:

[CLI] Create a local test file:

echo "NovaTech financial report - $(date)" > nvt-report-sample.txt

[CLI] Authenticate AzCopy with Microsoft Entra ID:

azcopy login

[CLI] Upload the file to the nvt-reports container using Microsoft Entra token authentication (no SAS, no access key):

azcopy copy nvt-report-sample.txt \
"https://<sa-name>.blob.core.windows.net/nvt-reports/"

Validation Criteria:

# Should return JSON object with name = nvt-report-sample.txt without error:
az storage blob show --name nvt-report-sample.txt \
--container-name nvt-reports \
--account-name <sa-name> --auth-mode login

Step 24 β€” Create and configure a file share in Azure Files​

Context: NovaTech application servers need an SMB file share to store shared configuration files. Tasks:

[CLI] Create the file share nvt-appconfig in the production storage account with 100 GiB quota using az storage share-rm create:

ParameterValue
--namenvt-appconfig
--quota100
--storage-account<sa-name>
--resource-groupnvt-rg-main

[CLI] Create the configs directory inside the file share using az storage directory create --share-name nvt-appconfig --name configs.

Validation Criteria:

# Should return Name = nvt-appconfig, Quota = 100:
az storage share-rm show --name nvt-appconfig \
--storage-account <sa-name> --resource-group nvt-rg-main \
--query "{Name:name,Quota:shareQuota}"

Step 25 β€” Create and configure containers in Azure Blob Storage​

Context: In addition to financial reports, NovaTech will store application logs and build artifacts in Azure Blob Storage. Each data type requires a separate container with distinct access configurations.

Tasks:

[CLI] Create the container nvt-applogs in the production storage account with private access:

az storage container create --name nvt-applogs \
--account-name <sa-name> --public-access off

[CLI] Create the container nvt-artifacts with blob access (anonymous blob read only):

az storage container create --name nvt-artifacts \
--account-name <sa-name> --public-access blob

Validation Criteria:

# Should return nvt-applogs with null and nvt-artifacts with "blob":
az storage container list --account-name <sa-name> \
--query "[?name=='nvt-applogs' || name=='nvt-artifacts'].{Name:name,Access:properties.publicAccess}"

Step 26 β€” Configure storage tiers​

Context: Application logs older than 30 days are rarely accessed and should be moved to lower cost tiers. The blob nvt-report-sample.txt created in Step 23 will be manually moved to the Cool tier as validation of the process.

Tasks:

[CLI] Change the access tier of blob nvt-report-sample.txt in container nvt-reports to Cool:

az storage blob set-tier \
--name nvt-report-sample.txt \
--container-name nvt-reports \
--account-name <sa-name> \
--tier Cool

[CLI] Confirm the current tier of the blob:

az storage blob show --name nvt-report-sample.txt \
--container-name nvt-reports --account-name <sa-name> \
--query "properties.blobTier"

Validation Criteria:

# Should return "Cool":
az storage blob show --name nvt-report-sample.txt \
--container-name nvt-reports --account-name <sa-name> \
--query "properties.blobTier"

Step 27 β€” Configure soft delete for blobs and containers​

Context: A recent incident in another NovaTech project resulted in accidental deletion of critical blobs. The financial regulator requires protection against accidental deletion with a minimum retention of 7 days.

Tasks:

[CLI] Enable soft delete for blobs in the production storage account with 7 days retention:

az storage account blob-service-properties update \
--account-name <sa-name> \
--enable-delete-retention true \
--delete-retention-days 7

[CLI] Enable soft delete for containers with 7 days retention:

az storage account blob-service-properties update \
--account-name <sa-name> \
--enable-container-delete-retention true \
--container-delete-retention-days 7

Validation Criteria:

# Both should return enabled = true and days = 7:
az storage account blob-service-properties show --account-name <sa-name> \
--query "{BlobDelete:deleteRetentionPolicy,ContainerDelete:containerDeleteRetentionPolicy}"

Step 28 β€” Configure snapshots and soft delete for Azure Files​

Context: The file share nvt-appconfig contains critical configuration files that must be protected with snapshots and soft delete, ensuring the ability to recover previous versions.

Tasks:

[CLI] Enable soft delete for the file share nvt-appconfig with 7 days retention:

az storage account file-service-properties update \
--account-name <sa-name> \
--enable-delete-retention true \
--delete-retention-days 7

[CLI] Create a manual snapshot of the file share nvt-appconfig using az storage share snapshot --name nvt-appconfig --account-name <sa-name>.

Validation Criteria:

# Should return enabled = true, days = 7:
az storage account file-service-properties show --account-name <sa-name> \
--query "shareDeleteRetentionPolicy"

# Should return array with at least 1 snapshot timestamp:
az storage share list --account-name <sa-name> \
--include-snapshots \
--query "[?name=='nvt-appconfig' && snapshot!=null].snapshot"

Step 29 β€” Configure blob lifecycle management​

Context: To automatically control storage costs, NovaTech defines a lifecycle policy: blobs in the nvt-applogs container should be moved to Cool after 30 days, to Archive after 90 days and deleted after 365 days.

Tasks:

[CLI] Create the lifecycle management policy in the production storage account. The --policy parameter accepts a JSON with the rules structure:

{
"rules": [
{
"name": "nvt-logs-lifecycle",
"enabled": true,
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["nvt-applogs/"]
},
"actions": {
"baseBlob": {
"tierToCool": { "daysAfterModificationGreaterThan": 30 },
"tierToArchive": { "daysAfterModificationGreaterThan": 90 },
"delete": { "daysAfterModificationGreaterThan": 365 }
}
}
}
}
]
}

Use az storage account management-policy create with --policy @nvt-lifecycle-policy.json.

Validation Criteria:

# Should return ["nvt-logs-lifecycle"]:
az storage account management-policy show \
--account-name <sa-name> --resource-group nvt-rg-main \
--query "policy.rules[?name=='nvt-logs-lifecycle'].name"

Step 30 β€” Configure blob versioning​

Context: Blob versioning was enabled in Step 21 as a prerequisite for object replication. In this step, the team validates the behavior by creating a second version of blob nvt-report-sample.txt and confirming that both versions coexist.

Tasks:

[CLI] Modify the content of the local file and upload it again to container nvt-reports, overwriting the existing blob with --overwrite true via az storage blob upload or azcopy copy.

[CLI] List the versions of blob nvt-report-sample.txt using the parameter --include v:

az storage blob list --container-name nvt-reports \
--account-name <sa-name> --include v \
--query "[?name=='nvt-report-sample.txt'].{Name:name,Version:versionId}" \
--auth-mode login

Validation Criteria:

# Should return 2 or more objects with the same name and distinct versionId:
az storage blob list --container-name nvt-reports \
--account-name <sa-name> --include v \
--query "[?name=='nvt-report-sample.txt'].{Name:name,Version:versionId}" \
--auth-mode login

Domain 3 β€” Infrastructure as Code (IaC)​

Prerequisite

az bicep install && az bicep version β€” Install Bicep CLI before starting this section.

Step 31 β€” Interpret and modify ARM templates and Bicep files​

Context: NovaTech adopts IaC as the standard for all production resources. The team will export the production resource group as an ARM template, convert it to Bicep and modify it to include a new Public IP resource.

Tasks:

[CLI] Export the ARM template from resource group nvt-rg-main:

az group export --name nvt-rg-main --output-file nvt-rg-main-template.json

[CLI] Convert the ARM template to Bicep:

az bicep decompile --file nvt-rg-main-template.json

[IaC] In the generated Bicep file nvt-rg-main-template.bicep, add the resource block below filling in the indicated fields:

resource nvtPublicIP 'Microsoft.Network/publicIPAddresses@2023-04-01' = {
name: // FILL IN: 'nvt-pip-main'
location: // FILL IN: East US region
sku: {
name: // FILL IN: Standard SKU
}
properties: {
publicIPAllocationMethod: // FILL IN: Static
}
}

[CLI] Validate the syntax of the modified Bicep file:

az bicep build --file nvt-rg-main-template.bicep

Validation Criteria:

# Should return number greater than 0:
grep -c "^resource " nvt-rg-main-template.bicep

# Should complete without syntax errors:
az bicep build --file nvt-rg-main-template.bicep

Step 32 β€” Deploy resources with Bicep template​

Context: With the Bicep file validated, the team deploys the Public IP to resource group nvt-rg-main. This is the first network resource provisioned via IaC, and the resulting IP will be used by the VM in the following steps.

Tasks:

[CLI] Deploy the Bicep file to resource group nvt-rg-main in Incremental mode:

az deployment group create \
--resource-group nvt-rg-main \
--template-file nvt-rg-main-template.bicep \
--mode Incremental

[CLI] Capture the public IP address allocated after deployment:

az network public-ip show --name nvt-pip-main \
--resource-group nvt-rg-main --query "ipAddress"

Validation Criteria:

# Should return Method = Static, SKU = Standard and populated IP:
az network public-ip show --name nvt-pip-main \
--resource-group nvt-rg-main \
--query "{IP:ipAddress,Method:publicIPAllocationMethod,SKU:sku.name}"

Domain 4 β€” Compute and Containers​

Step 33 β€” Create and configure Virtual Machines​

Context: NovaTech will deploy the first Windows application server in resource group nvt-rg-main. The VM must be in the VNet created in Step 14, in a dedicated subnet for compute, using the Public IP created in Step 32.

Tasks:

[CLI] Create subnet nvt-snet-compute with prefix 10.10.2.0/24 in VNet nvt-vnet-main using az network vnet subnet create.

[CLI] Create VM nvt-vm-app01 in resource group nvt-rg-main, region East US, with the parameters:

ParameterValue
--imageWin2022Datacenter
--sizeStandard_B2s
--vnet-namenvt-vnet-main
--subnetnvt-snet-compute
--admin-usernamenvtadmin
--public-ip-addressnvt-pip-main

Wait for completion with --no-wait false. Capture the VM Resource ID at the end.

Validation Criteria:

# Should return ["VM running"]:
az vm get-instance-view --name nvt-vm-app01 --resource-group nvt-rg-main \
--query "instanceView.statuses[?starts_with(code,'PowerState/')].displayStatus"

Step 34 β€” Configure encryption at host for VMs​

Context: The financial regulator requires that all temporary disks and disk caches of production VMs be encrypted at the host. The team must enable this feature on VM nvt-vm-app01.

Tasks:

[CLI] Register the necessary feature provider and wait for Registered state:

az feature register --namespace Microsoft.Compute --name EncryptionAtHost
az feature show --namespace Microsoft.Compute --name EncryptionAtHost

[CLI] Stop (deallocate) VM nvt-vm-app01:

az vm deallocate --name nvt-vm-app01 --resource-group nvt-rg-main

[CLI] Enable encryption at host on the VM and restart it:

az vm update --name nvt-vm-app01 --resource-group nvt-rg-main \
--set securityProfile.encryptionAtHost=true

az vm start --name nvt-vm-app01 --resource-group nvt-rg-main

Validation Criteria:

# Should return true:
az vm show --name nvt-vm-app01 --resource-group nvt-rg-main \
--query "securityProfile.encryptionAtHost"

Step 35 β€” Manage VM disks​

Context: VM nvt-vm-app01 needs an additional data disk to store application files separately from the operating system disk, following NovaTech's data segregation practice.

Tasks:

[CLI] Create managed disk nvt-disk-appdata in resource group nvt-rg-main, region East US, with SKU Premium_LRS and size of 128 GiB using az disk create.

[CLI] Attach the disk to VM nvt-vm-app01 using az vm disk attach --vm-name nvt-vm-app01 --name nvt-disk-appdata.

Validation Criteria:

# Should return object with Name = nvt-disk-appdata and Size = 128:
az vm show --name nvt-vm-app01 --resource-group nvt-rg-main \
--query "storageProfile.dataDisks[].{Name:name,Size:diskSizeGb,SKU:managedDisk.storageAccountType}"

Step 36 β€” Deploy VMs in Availability Zones and Availability Sets​

Context: To ensure high availability of the application service, NovaTech requires a second VM in a different Availability Zone.

Tasks:

[CLI] Create VM nvt-vm-app02 in zone 2 in resource group nvt-rg-main, with the same image, size and VNet/subnet as nvt-vm-app01, but without Public IP:

az vm create --name nvt-vm-app02 \
--resource-group nvt-rg-main \
--zone 2 \
--image Win2022Datacenter \
--size Standard_B2s \
--vnet-name nvt-vnet-main \
--subnet nvt-snet-compute \
--admin-username nvtadmin \
--public-ip-address ""

[CLI] Create Availability Set nvt-avset-app in resource group nvt-rg-main to document grouping intent:

az vm availability-set create --name nvt-avset-app \
--resource-group nvt-rg-main \
--platform-fault-domain-count 2 \
--platform-update-domain-count 5

Validation Criteria:

# Should return ["2"]:
az vm show --name nvt-vm-app02 --resource-group nvt-rg-main --query "zones"

# Should return FaultDomains = 2, UpdateDomains = 5:
az vm availability-set show --name nvt-avset-app --resource-group nvt-rg-main \
--query "{FaultDomains:platformFaultDomainCount,UpdateDomains:platformUpdateDomainCount}"

Step 37 β€” Deploy and configure Azure Virtual Machine Scale Sets​

Context: NovaTech's transaction processing service has predictable load spikes. For this component, a VMSS with autoscaling is more suitable than individual VMs.

Tasks:

[CLI] Create VMSS nvt-vmss-proc in resource group nvt-rg-main, region East US, with the parameters:

ParameterValue
--imageUbuntu2204
--vm-skuStandard_B1s
--instance-count2
--vnet-namenvt-vnet-main
--subnetnvt-snet-compute
--upgrade-policy-modeAutomatic

Use az vmss create.

[CLI] Configure autoscaling policy using az monitor autoscale create followed by az monitor autoscale rule create:

  • Scale-out: Average CPU > 70% for 5 min β†’ add 1 instance
  • Scale-in: Average CPU < 30% for 5 min β†’ remove 1 instance
  • Minimum: 2, Maximum: 5 instances

Validation Criteria:

# Should return Capacity = 2, UpgradePolicy = Automatic:
az vmss show --name nvt-vmss-proc --resource-group nvt-rg-main \
--query "{Name:name,Capacity:sku.capacity,UpgradePolicy:upgradePolicy.mode}"

# Should return array with autoscale profile name:
az monitor autoscale list --resource-group nvt-rg-main \
--query "[?contains(targetResourceUri,'nvt-vmss-proc')].name"

Step 38 β€” Create and manage Azure Container Registry​

Context: NovaTech adopts containers for clients' modern applications. The DevOps team needs a private registry to store Docker images of the applications.

Prerequisite

Docker Desktop installed and running. Check with: docker --version

Tasks:

[CLI] Create Azure Container Registry nvtregistry<suffix> in resource group nvt-rg-main, region East US, SKU Standard, admin disabled:

az acr create --name nvtregistry<suffix> \
--resource-group nvt-rg-main \
--sku Standard \
--admin-enabled false

[CLI] Build a simple image using ACR Tasks with a minimal Dockerfile (FROM mcr.microsoft.com/hello-world:latest):

az acr build --registry <registry-name> \
--image nvt-hello:v1 .

[CLI] List available images in the registry:

az acr repository list --name <registry-name>

Validation Criteria:

# Should return object with name = nvt-hello and tags listed:
az acr repository show --name <registry-name> --repository nvt-hello

Step 39 β€” Provision containers with Azure Container Instances​

Context: For a stateless auxiliary reporting service, NovaTech will use Azure Container Instances for on-demand executions without managing cluster infrastructure.

Tasks:

[CLI] Enable admin user on registry and capture credentials:

az acr update --name <registry-name> --admin-enabled true
az acr credential show --name <registry-name>

[CLI] Create Container Instance nvt-aci-report in resource group nvt-rg-main, region East US, using image nvt-hello:v1 from registry:

az container create \
--name nvt-aci-report \
--resource-group nvt-rg-main \
--image <login-server>/nvt-hello:v1 \
--cpu 1 --memory 1 \
--registry-login-server <login-server> \
--registry-username <username> \
--registry-password <password>

Validation Criteria:

# Should return Status = Succeeded (or Running) with correct Image:
az container show --name nvt-aci-report --resource-group nvt-rg-main \
--query "{Status:instanceView.state,Image:containers[0].image}"

Step 40 β€” Provision containers with Azure Container Apps​

Context: For the main API service of clients' financial applications, NovaTech adopts Azure Container Apps, which offers automatic scaling and revision management.

Tasks:

[CLI] Create Container Apps Environment nvt-cae-main in resource group nvt-rg-main, region East US:

az containerapp env create --name nvt-cae-main \
--resource-group nvt-rg-main --location eastus

[CLI] Create Container App nvt-ca-api in environment nvt-cae-main using public image mcr.microsoft.com/azuredocs/containerapps-helloworld:latest:

az containerapp create --name nvt-ca-api \
--resource-group nvt-rg-main \
--environment nvt-cae-main \
--image mcr.microsoft.com/azuredocs/containerapps-helloworld:latest \
--target-port 80 --ingress external \
--min-replicas 1 --max-replicas 3

[CLI] Get public URL and validate with curl -I https://<fqdn>:

az containerapp show --name nvt-ca-api --resource-group nvt-rg-main \
--query "properties.configuration.ingress.fqdn"

Validation Criteria:

# Should return Status = Running with FQDN populated:
az containerapp show --name nvt-ca-api --resource-group nvt-rg-main \
--query "{Status:properties.runningStatus,FQDN:properties.configuration.ingress.fqdn}"

Step 41 β€” Manage sizing and scaling for containers​

Context: The operations team identified that Container App nvt-ca-api needs more CPU during peak hours. The resource profile should be adjusted and the scaling range modified to support up to 5 replicas.

Tasks:

[CLI] Update Container App nvt-ca-api to use 0.5 CPUs, 1.0Gi memory per replica and max-replicas of 5:

az containerapp update --name nvt-ca-api \
--resource-group nvt-rg-main \
--cpu 0.5 --memory 1.0Gi \
--min-replicas 1 --max-replicas 5

[CLI] Update Container Instance nvt-aci-report to use 2 CPUs (ACI doesn't have update β€” recreate with same parameters and --cpu 2).

Validation Criteria:

# Should return cpu = "0.5" and memory = "1Gi":
az containerapp show --name nvt-ca-api --resource-group nvt-rg-main \
--query "properties.template.containers[0].resources"

Domain 5 β€” App Service​

Step 42 β€” Create and configure Azure App Service​

Context: NovaTech's web portal for financial clients access will be hosted on Azure App Service. The team must provision the plan, App Service and a staging slot with production configurations required by the company.

Cost

App Service P1v3 generates ~USD 0.16/h. Reduce to F1 between sessions if necessary.

Tasks:

[CLI] Create App Service Plan nvt-asp-main in resource group nvt-rg-main, region East US, SKU P1v3, Linux:

az appservice plan create --name nvt-asp-main \
--resource-group nvt-rg-main \
--is-linux --sku P1v3

[CLI] Create Web App nvt-webapp-portal<suffix> on plan nvt-asp-main, runtime NODE:18-lts:

az webapp create --name nvt-webapp-portal<suffix> \
--resource-group nvt-rg-main \
--plan nvt-asp-main \
--runtime "NODE:18-lts"

[CLI] Create deployment slot staging:

az webapp deployment slot create \
--name <webapp-name> \
--resource-group nvt-rg-main \
--slot staging

Validation Criteria:

# Should return State = Running:
az webapp show --name <webapp-name> --resource-group nvt-rg-main \
--query "{State:state,Plan:appServicePlanId}"

# Should return ["staging"]:
az webapp deployment slot list --name <webapp-name> \
--resource-group nvt-rg-main --query "[].name"

Step 43 β€” Configure scaling for App Service Plan​

Context: NovaTech's web portal has traffic spikes at the end of the month when clients check financial reports. The App Service Plan should automatically scale based on CPU utilization.

Tasks:

[CLI] Configure autoscaling on App Service Plan nvt-asp-main using az monitor autoscale create:

  • Minimum: 1 instance, Maximum: 3 instances, Default: 1
  • Scale-out: CPU > 70% for 10 min β†’ add 1 instance
  • Scale-in: CPU < 30% for 10 min β†’ remove 1 instance

The --resource parameter should point to the App Service Plan's Resource ID.

Validation Criteria:

# Should return Min = "1" and Max = "3":
az monitor autoscale list --resource-group nvt-rg-main \
--query "[?contains(targetResourceUri,'nvt-asp-main')].{Name:name,Min:profiles[0].capacity.minimum,Max:profiles[0].capacity.maximum}"

Step 44 β€” Configure TLS and certificates for App Service​

Context: NovaTech's web portal must be accessed exclusively via HTTPS. The team must enable forced HTTP to HTTPS redirection and configure the minimum TLS version according to financial security requirements.

Tasks:

[CLI] Enable forced HTTP to HTTPS redirection:

az webapp update --name <webapp-name> \
--resource-group nvt-rg-main --https-only true

[CLI] Configure minimum TLS version as 1.2:

az webapp config set --name <webapp-name> \
--resource-group nvt-rg-main --min-tls-version 1.2

Validation Criteria:

# Should return true:
az webapp show --name <webapp-name> \
--resource-group nvt-rg-main --query "httpsOnly"

# Should return "1.2":
az webapp config show --name <webapp-name> \
--resource-group nvt-rg-main --query "minTlsVersion"

Step 45 β€” Configure backup for App Service​

Context: The financial regulator requires the Web App to have daily automatic backups retained for 30 days. The backup should be stored in the production storage account created earlier.

Tasks:

[Portal] Configure automatic backup in: App Services β†’ <webapp-name> β†’ Backup

ParameterValue
Storage accountnvtprodsa<suffix>
Containernvt-webapp-backup (create new)
FrequencyDaily
Retention30 days

[CLI] Verify Web App backup configuration:

az webapp config backup show \
--resource-group nvt-rg-main --webapp-name <webapp-name>

Validation Criteria:

# Should return object with configured frequency and retention:
az webapp config backup show \
--resource-group nvt-rg-main --webapp-name <webapp-name> \
--query "backupSchedule"

Step 46 β€” Configure deployment slots for App Service​

Context: NovaTech's deployment process uses the slot swap strategy: code is deployed to the staging slot and, after validation, promoted to production with zero downtime.

Tasks:

[CLI] Deploy a test application to the staging slot using az webapp up --slot staging with a simple Node.js application, or configure source control on the slot via az webapp deployment source config.

[CLI] Execute slot swap from staging slot to production:

az webapp deployment slot swap \
--name <webapp-name> \
--resource-group nvt-rg-main \
--slot staging \
--target-slot production

Validation Criteria:

# Should return staging slot in Running state:
az webapp deployment slot list --name <webapp-name> \
--resource-group nvt-rg-main \
--query "[].{Slot:name,State:state}"

Step 47 β€” Configure networking settings for App Service​

Context: To ensure the Web App accesses NovaTech's internal resources without exposing traffic to the public internet, the team must configure VNet Integration for the compute subnet.

Tasks:

[CLI] Create subnet nvt-snet-webapp with prefix 10.10.3.0/24 in VNet nvt-vnet-main, with delegation for Microsoft.Web/serverFarms:

az network vnet subnet create \
--name nvt-snet-webapp \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network \
--address-prefix 10.10.3.0/24 \
--delegations Microsoft.Web/serverFarms

[CLI] Configure Web App VNet Integration:

az webapp vnet-integration add \
--name <webapp-name> \
--resource-group nvt-rg-main \
--vnet nvt-vnet-main \
--subnet nvt-snet-webapp

Validation Criteria:

# Should return object with correct VNet and subnet:
az webapp vnet-integration list --name <webapp-name> \
--resource-group nvt-rg-main \
--query "[].{VNet:vnetResourceId,Subnet:subnet}"

Domain 6 β€” Networks​

Step 48 β€” Create and configure Virtual Networks and subnets​

Context: NovaTech needs a second VNet in Brazil South for staging environment resources. This VNet will have specific subnets for compute and storage, following the same addressing pattern as the main VNet.

Tasks:

[CLI] Create VNet nvt-vnet-staging in resource group nvt-rg-network, region Brazil South, with address space 10.20.0.0/16 using az network vnet create.

[CLI] Create the subnets below within the staging VNet:

NamePrefix
nvt-snet-stg-compute10.20.1.0/24
nvt-snet-stg-storage10.20.2.0/24

Validation Criteria:

# Should return 2 objects with correct prefixes:
az network vnet subnet list --vnet-name nvt-vnet-staging \
--resource-group nvt-rg-network \
--query "[].{Name:name,Prefix:addressPrefix}"

Step 49 β€” Configure Virtual Network Peering​

Context: The staging environment needs to access shared services in the production resource group (like Key Vault and storage account). Peering between nvt-vnet-main and nvt-vnet-staging will enable this connectivity.

Tasks:

[CLI] Create peering from nvt-vnet-main to nvt-vnet-staging:

az network vnet peering create \
--name nvt-peer-main-to-staging \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network \
--remote-vnet <resource-id-of-nvt-vnet-staging> \
--allow-vnet-access

[CLI] Create reciprocal peering from nvt-vnet-staging to nvt-vnet-main (peering requires creation on both sides).

Validation Criteria:

# Both should return "Connected":
az network vnet peering show --name nvt-peer-main-to-staging \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network --query "peeringState"

az network vnet peering show --name nvt-peer-staging-to-main \
--vnet-name nvt-vnet-staging \
--resource-group nvt-rg-network --query "peeringState"

Step 50 β€” Configure Public IP Addresses​

Context: The Public IP nvt-pip-main was created via Bicep in Step 32. The team must create a second Public IP for the Load Balancer that will be deployed in Step 57, configuring it with DNS label for name access.

Tasks:

[CLI] Create Public IP nvt-pip-lb in resource group nvt-rg-network, region East US, SKU Standard, allocation method Static, with globally unique DNS label:

az network public-ip create \
--name nvt-pip-lb \
--resource-group nvt-rg-network \
--sku Standard \
--allocation-method Static \
--dns-name nvt-lb-<unique-suffix>

[CLI] Confirm assigned FQDN:

az network public-ip show --name nvt-pip-lb \
--resource-group nvt-rg-network --query "dnsSettings.fqdn"

Validation Criteria:

# Should return SKU = Standard and FQDN in format *.eastus.cloudapp.azure.com:
az network public-ip show --name nvt-pip-lb \
--resource-group nvt-rg-network \
--query "{IP:ipAddress,FQDN:dnsSettings.fqdn,SKU:sku.name}"

Step 51 β€” Configure user-defined routes​

Context: NovaTech's security policy requires all outbound traffic from application VMs to pass through a Network Virtual Appliance (simulated). Traffic from subnet nvt-snet-compute to the internet should be routed through a specific internal IP.

Tasks:

[CLI] Create Route Table nvt-rt-compute in resource group nvt-rg-network, region East US, with BGP route propagation disabled:

az network route-table create \
--name nvt-rt-compute \
--resource-group nvt-rg-network \
--disable-bgp-route-propagation true

[CLI] Add route for prefix 0.0.0.0/0 with next hop VirtualAppliance and IP 10.10.0.4 (simulated NVA IP):

az network route-table route create \
--route-table-name nvt-rt-compute \
--resource-group nvt-rg-network \
--name nvt-route-internet \
--address-prefix 0.0.0.0/0 \
--next-hop-type VirtualAppliance \
--next-hop-ip-address 10.10.0.4

[CLI] Associate the Route Table to the subnet nvt-snet-compute:

az network vnet subnet update \
--name nvt-snet-compute \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network \
--route-table nvt-rt-compute

Validation Criteria:

# Should return Resource ID containing nvt-rt-compute:
az network vnet subnet show --name nvt-snet-compute \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network --query "routeTable.id"

Step 52 β€” Create and configure NSGs and Application Security Groups​

Context: NovaTech needs to control network traffic between application layers. An NSG should protect the compute subnet, and Application Security Groups (ASG) will allow logical rules based on server function.

Tasks:

[CLI] Create the ASG nvt-asg-appservers in resource group nvt-rg-network, region East US:

az network asg create --name nvt-asg-appservers \
--resource-group nvt-rg-network

[CLI] Create the NSG nvt-nsg-compute in resource group nvt-rg-network and add the rules:

PriorityNameDirectionSourceDestPortAction
100Allow-HTTP-InboundInbound*ASG nvt-asg-appservers80Allow
110Allow-HTTPS-InboundInbound*ASG nvt-asg-appservers443Allow
4000Deny-All-InboundInbound***Deny

Use az network nsg rule create for each rule.

[CLI] Associate the NSG nvt-nsg-compute to subnet nvt-snet-compute:

az network vnet subnet update --name nvt-snet-compute \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network \
--network-security-group nvt-nsg-compute

Validation Criteria:

# Should return 3 lines with priorities 100, 110 and 4000:
az network nsg rule list --nsg-name nvt-nsg-compute \
--resource-group nvt-rg-network \
--query "[].{Name:name,Priority:priority,Action:access}" --output table

Step 53 β€” Evaluate effective security rules in NSGs​

Context: After NSG configuration, the security team needs to verify the effective rules applied to the NIC of VM nvt-vm-app01, confirming that the NSG rules and Azure default rules are combining correctly.

Tasks:

[CLI] Get the NIC name of VM nvt-vm-app01:

az vm show --name nvt-vm-app01 --resource-group nvt-rg-main \
--query "networkProfile.networkInterfaces[0].id"

[CLI] List the effective security rules applied to the NIC:

az network nic list-effective-nsg \
--name <nic-name> --resource-group nvt-rg-main

[CLI] Identify in the output which rules are from NSG nvt-nsg-compute and which are Azure default rules. Record the result.

Validation Criteria:

# Should return object with Name = Allow-HTTP-Inbound and Priority = 100:
az network nic list-effective-nsg --name <nic-name> \
--resource-group nvt-rg-main \
--query "effectiveSecurityRules[?name=='Allow-HTTP-Inbound'].{Name:name,Priority:priority}"

Step 54 β€” Implement Azure Bastion​

Context: NovaTech has eliminated direct RDP/SSH access via internet to production VMs. Azure Bastion will be deployed to provide secure browser-based access without public port exposure.

Cost

Azure Bastion generates ~USD 0.19/h. Delete the resource after use during the lab.

Tasks:

[CLI] Create subnet AzureBastionSubnet in VNet nvt-vnet-main with prefix 10.10.254.0/27. The subnet name must be exactly AzureBastionSubnet.

[CLI] Create Public IP nvt-pip-bastion in resource group nvt-rg-network, region East US, SKU Standard, allocation method Static.

[CLI] Create Azure Bastion nvt-bastion-main in resource group nvt-rg-network. The deployment takes approximately 10 minutes:

az network bastion create \
--name nvt-bastion-main \
--resource-group nvt-rg-network \
--vnet-name nvt-vnet-main \
--public-ip-address nvt-pip-bastion

Validation Criteria:

# Should return State = Succeeded:
az network bastion show --name nvt-bastion-main \
--resource-group nvt-rg-network \
--query "{State:provisioningState,SKU:sku.name}"

Step 55 β€” Configure service endpoints and private endpoints​

Context: To reinforce security for storage account and Key Vault access, the team must configure private endpoints for both services within the main VNet, eliminating data traffic via public internet.

Tasks:

[CLI] Create subnet nvt-snet-privatelink with prefix 10.10.4.0/24 in VNet nvt-vnet-main, with network policies disabled:

az network vnet subnet create \
--name nvt-snet-privatelink \
--vnet-name nvt-vnet-main \
--resource-group nvt-rg-network \
--address-prefix 10.10.4.0/24 \
--disable-private-endpoint-network-policies true

[CLI] Create the private endpoint for the production storage account in subnet nvt-snet-privatelink:

az network private-endpoint create \
--name nvt-pe-storage \
--resource-group nvt-rg-main \
--vnet-name nvt-vnet-main \
--subnet nvt-snet-privatelink \
--private-connection-resource-id <storage-resource-id> \
--group-id blob \
--connection-name nvt-pe-storage-conn

[CLI] Create Private DNS Zone privatelink.blob.core.windows.net and link it to VNet nvt-vnet-main:

az network private-dns zone create \
--name privatelink.blob.core.windows.net \
--resource-group nvt-rg-network

az network private-dns link vnet create \
--name nvt-dns-link-storage \
--zone-name privatelink.blob.core.windows.net \
--resource-group nvt-rg-network \
--virtual-network nvt-vnet-main \
--registration-enabled false

[CLI] Create the DNS zone group for the private endpoint:

az network private-endpoint dns-zone-group create \
--endpoint-name nvt-pe-storage \
--resource-group nvt-rg-main \
--name nvt-pe-storage-dns \
--private-dns-zone privatelink.blob.core.windows.net \
--zone-name blob

Validation Criteria:

# Should return ConnState = Approved, State = Succeeded:
az network private-endpoint show --name nvt-pe-storage \
--resource-group nvt-rg-main \
--query "{State:provisioningState,ConnState:privateLinkServiceConnections[0].privateLinkServiceConnectionState.status}"

Step 56 β€” Configure Azure DNS​

Context: NovaTech uses the fictional domain novatech.local for internal name resolution between resources. The Azure DNS Private Zone should be configured with A records for VMs and a CNAME record for the Web App.

Tasks:

[CLI] Create Private DNS Zone novatech.local in resource group nvt-rg-network:

az network private-dns zone create \
--name novatech.local \
--resource-group nvt-rg-network

[CLI] Link the novatech.local zone to VNet nvt-vnet-main with auto-registration enabled:

az network private-dns link vnet create \
--name nvt-dns-link-main \
--zone-name novatech.local \
--resource-group nvt-rg-network \
--virtual-network nvt-vnet-main \
--registration-enabled true

[CLI] Create an A record pointing app01.novatech.local to the private IP of VM nvt-vm-app01. Capture the IP with az vm show --query "privateIps" -d:

az network private-dns record-set a add-record \
--zone-name novatech.local \
--resource-group nvt-rg-network \
--record-set-name app01 \
--ipv4-address <vm-private-ip>

Validation Criteria:

# Should return array with the private IP of VM nvt-vm-app01:
az network private-dns record-set a show \
--zone-name novatech.local \
--resource-group nvt-rg-network \
--name app01 --query "aRecords"

Step 57 β€” Configure internal and public Load Balancer​

Context: The two application VMs (nvt-vm-app01 and nvt-vm-app02) should receive balanced HTTP traffic. The public Load Balancer will use the Public IP nvt-pip-lb created in Step 50 and distribute traffic on port 80 between the two VMs.

Tasks:

[CLI] Create public Load Balancer nvt-lb-public in resource group nvt-rg-network, SKU Standard, with frontend IP using nvt-pip-lb:

az network lb create \
--name nvt-lb-public \
--resource-group nvt-rg-network \
--sku Standard \
--public-ip-address nvt-pip-lb \
--frontend-ip-name nvt-lb-frontend

[CLI] Create backend pool nvt-lb-backendpool and add the NICs of VMs nvt-vm-app01 and nvt-vm-app02 to the pool using az network lb address-pool create and az network nic ip-config address-pool add.

[CLI] Create HTTP health probe on port 80 with path / and 15 seconds interval, and the load balancing rule for port 80:

az network lb probe create \
--lb-name nvt-lb-public \
--resource-group nvt-rg-network \
--name nvt-lb-probe --protocol Http --port 80 --path / --interval 15

az network lb rule create \
--lb-name nvt-lb-public \
--resource-group nvt-rg-network \
--name nvt-lb-rule-http \
--frontend-ip-name nvt-lb-frontend \
--backend-pool-name nvt-lb-backendpool \
--probe-name nvt-lb-probe \
--protocol Tcp --frontend-port 80 --backend-port 80

Validation Criteria:

# Should return FE, BE and Probe filled with the defined names:
az network lb show --name nvt-lb-public --resource-group nvt-rg-network \
--query "{FE:frontendIpConfigurations[0].name,BE:backendAddressPools[0].name,Probe:probes[0].name}"

Step 58 β€” Troubleshoot network connectivity​

Context: The operations team reported that VM nvt-vm-app02 cannot reach the production storage account via private endpoint. Azure Network Watcher should be used to diagnose the connectivity issue.

Tasks:

[CLI] Check if Network Watcher is enabled in region East US. If not, enable it:

az network watcher list
az network watcher configure --resource-group nvt-rg-network --locations eastus --enabled true

[CLI] Run a connectivity test from VM nvt-vm-app02 to the storage account private endpoint:

az network watcher test-connectivity \
--source-resource <vm-app02-resource-id> \
--dest-address <private-endpoint-ip> \
--dest-port 443

[CLI] Analyze the result and identify the component that blocks or allows the connection.

Validation Criteria:

# Should return connectionStatus filled (Reachable or Unreachable) with hops list:
az network watcher test-connectivity \
--source-resource <vm-app02-id> \
--dest-address <private-endpoint-ip> \
--dest-port 443 \
--query "{Status:connectionStatus,Hops:hops[*].type}"

Domain 7 β€” Monitoring​

Step 59 β€” Interpret metrics in Azure Monitor​

Context: NovaTech's operations team needs visibility into the performance of VM nvt-vm-app01. CPU and memory metrics should be queried via CLI to establish a performance baseline.

Tasks:

[CLI] Query the Percentage CPU metric of VM nvt-vm-app01 for the last 1 hour interval, with 5-minute granularity and Average aggregation:

az monitor metrics list \
--resource <vm-resource-id> \
--metric "Percentage CPU" \
--interval PT5M \
--aggregation Average

[CLI] Query the Available Memory Bytes metric of the same VM for the same period.

Validation Criteria:

# Should return array with timeStamp and average (may be 0 if VM idle):
az monitor metrics list --resource <vm-id> \
--metric "Percentage CPU" \
--interval PT5M --aggregation Average \
--query "value[0].timeseries[0].data[-3:].{Time:timeStamp,Avg:average}"

Step 60 β€” Configure log settings in Azure Monitor​

Context: To meet financial audit requirements, all administrative and diagnostic events from VMs and the storage account should be sent to a centralized Log Analytics Workspace.

Tasks:

[CLI] Create Log Analytics Workspace nvt-law-main in resource group nvt-rg-main, region East US, 30-day retention:

az monitor log-analytics workspace create \
--name nvt-law-main \
--resource-group nvt-rg-main \
--retention-time 30

[CLI] Enable Diagnostic Settings on VM nvt-vm-app01 to send logs to workspace nvt-law-main:

az monitor diagnostic-settings create \
--resource <vm-resource-id> \
--workspace <workspace-resource-id> \
--name "nvt-diag-vm"

[CLI] Enable Diagnostic Settings on the production storage account, sending Transaction metrics and StorageRead, StorageWrite logs to the workspace. The --resource points to the blob service endpoint of the storage account (add /blobServices/default to the SA Resource ID).

Validation Criteria:

# Should return object with Workspace filled with the workspace Resource ID:
az monitor diagnostic-settings show \
--resource <vm-id> --name "nvt-diag-vm" \
--query "{Name:name,Workspace:workspaceId}"

Step 61 β€” Query and analyze logs in Azure Monitor​

Context: With logs being collected in workspace nvt-law-main, the security team needs to execute KQL queries to check for authentication errors and monitor VM activity.

Tasks:

[CLI] Execute KQL query in workspace nvt-law-main to list the last 10 heartbeats:

az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "Heartbeat | top 10 by TimeGenerated desc"

[CLI] Execute event count query by Computer in the last 24 hours:

az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "Heartbeat | summarize count() by Computer | order by count_ desc"

Validation Criteria:

# Should return array (empty or with data) without API error:
az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "Heartbeat | take 1" \
--query "tables[0].rows"

Step 62 β€” Configure alert rules, action groups and alert processing rules​

Context: The operations team should be notified immediately when CPU on any production VM exceeds 85% for more than 5 minutes. An action group with email should be configured and linked to an alert rule.

Tasks:

[CLI] Create Action Group nvt-ag-ops in resource group nvt-rg-main with an email receiver:

az monitor action-group create \
--name nvt-ag-ops \
--resource-group nvt-rg-main \
--short-name nvtops \
--action email nvt-alert-email <your-email>

[CLI] Create a Metric Alert Rule nvt-alert-cpu-high that triggers when Percentage CPU > 85 for 5 minutes:

az monitor metrics alert create \
--name nvt-alert-cpu-high \
--resource-group nvt-rg-main \
--scopes <resource-id-da-vm-app01> \
--condition "avg Percentage CPU > 85" \
--window-size 5m \
--evaluation-frequency 1m \
--action <resource-id-do-action-group>

Validation Criteria:

# Should return Condition = 85.0, Enabled = true:
az monitor metrics alert show --name nvt-alert-cpu-high \
--resource-group nvt-rg-main \
--query "{Name:name,Enabled:enabled,Condition:criteria.allOf[0].threshold}"

Step 63 β€” Monitor VMs, storage accounts and networks with Azure Monitor Insights​

Context: The NovaTech operations dashboard should consolidate insights from VMs, storage accounts and network in a single visibility point. VM Insights should be enabled on VM nvt-vm-app01.

Tasks:

[CLI] Enable VM Insights on VM nvt-vm-app01 by associating it with workspace nvt-law-main. Install the monitoring extension:

az vm extension set \
--vm-name nvt-vm-app01 \
--resource-group nvt-rg-main \
--name MicrosoftMonitoringAgent \
--publisher Microsoft.EnterpriseCloud.Monitoring \
--settings '{"workspaceId": "<workspace-id>"}' \
--protected-settings '{"workspaceKey": "<workspace-key>"}'

Get workspace ID and key with az monitor log-analytics workspace get-shared-keys.

[Portal] Confirm that the state displays Monitored in: Azure Monitor β†’ Virtual Machines β†’ nvt-vm-app01 β†’ Insights.

Validation Criteria:

# Should return object with State = Succeeded:
az vm extension list --vm-name nvt-vm-app01 --resource-group nvt-rg-main \
--query "[?name=='MicrosoftMonitoringAgent' || name=='AzureMonitorWindowsAgent'].{Name:name,State:provisioningState}"

Step 64 β€” Use Azure Network Watcher and Connection Monitor​

Context: For continuous monitoring of connectivity between VMs and the production storage account, the team configures a Connection Monitor that will run automatic connectivity tests every 30 seconds.

Tasks:

[CLI] Create the Connection Monitor nvt-cm-vm-to-storage in Network Watcher for East US region:

az network watcher connection-monitor create \
--name nvt-cm-vm-to-storage \
--location eastus \
--source-resource <resource-id-da-vm-app01> \
--dest-address <ip-do-private-endpoint-storage> \
--dest-port 443 \
--monitoring-interval 30

[CLI] Query the current state of the Connection Monitor:

az network watcher connection-monitor query \
--name nvt-cm-vm-to-storage --location eastus

Validation Criteria:

# Should return State = Running or Monitoring:
az network watcher connection-monitor show \
--name nvt-cm-vm-to-storage --location eastus \
--query "{Name:name,State:monitoringStatus}"

Domain 8 β€” Backup and Disaster Recovery​

Step 65 β€” Create Recovery Services Vault and Backup Vault​

Context: The NovaTech business continuity plan requires dedicated backup vaults for VMs and for the storage account. A Recovery Services Vault will cover VMs, and a Backup Vault will cover modern workloads.

Tasks:

[CLI] Create the Recovery Services Vault nvt-rsv-main in resource group nvt-rg-main, East US region:

az backup vault create \
--name nvt-rsv-main \
--resource-group nvt-rg-main \
--location eastus

[CLI] Set the vault storage redundancy to GeoRedundant:

az backup vault backup-properties set \
--name nvt-rsv-main \
--resource-group nvt-rg-main \
--backup-storage-redundancy GeoRedundant

[CLI] Create the Backup Vault nvt-bv-main in resource group nvt-rg-main, East US region:

az dataprotection backup-vault create \
--vault-name nvt-bv-main \
--resource-group nvt-rg-main \
--location eastus \
--storage-setting "[{type:'LocallyRedundant',datastore-type:'VaultStore'}]"

Validation Criteria:

# Should return Name = nvt-rsv-main, Location = eastus:
az backup vault show --name nvt-rsv-main --resource-group nvt-rg-main \
--query "{Name:name,Location:location,SKU:sku.name}"

# Should return "nvt-bv-main":
az dataprotection backup-vault show \
--vault-name nvt-bv-main --resource-group nvt-rg-main --query "name"

Step 66 β€” Create and configure backup policy​

Context: With the vaults created, the team should configure backup policies for production VMs. The policy defines daily frequency, schedule and 30-day retention period, aligned with regulatory requirements.

Tasks:

[CLI] Get the default backup policy template for Azure VMs and save to file:

az backup policy get-default-for-vm \
--vault-name nvt-rsv-main \
--resource-group nvt-rg-main > nvt-backup-policy.json

[CLI] Modify the nvt-backup-policy.json file to adjust daily retention to 30 days and backup time to 02:00 UTC. Then create the policy in the vault:

az backup policy create \
--policy @nvt-backup-policy.json \
--vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--name "nvt-vm-backup-policy" \
--backup-management-type AzureIaasVM

Validation Criteria:

# Should return Name = nvt-vm-backup-policy, Type = AzureIaasVM:
az backup policy show --vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--name "nvt-vm-backup-policy" \
--query "{Name:name,Type:backupManagementType}"

Step 67 β€” Perform backup and restore operations with Azure Backup​

Context: VM nvt-vm-app01 should be protected immediately. The team enables backup, runs an on-demand backup and verifies available recovery points.

Tasks:

[CLI] Enable backup for VM nvt-vm-app01 in vault nvt-rsv-main using policy nvt-vm-backup-policy:

az backup protection enable-for-vm \
--vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--vm nvt-vm-app01 \
--policy-name "nvt-vm-backup-policy"

[CLI] Run an immediate (on-demand) backup and monitor status until completion:

# Get container name first:
az backup container list --vault-name nvt-rsv-main \
--resource-group nvt-rg-main --backup-management-type AzureIaasVM

az backup protection backup-now \
--vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--container-name <container-name> \
--item-name nvt-vm-app01 \
--retain-until <data-7-dias-a-frente>

# Monitor:
az backup job list --vault-name nvt-rsv-main \
--resource-group nvt-rg-main --status InProgress

[Portal] After completion, confirm in: Recovery Services vaults β†’ nvt-rsv-main β†’ Backup items β†’ Azure Virtual Machine. VM nvt-vm-app01 should appear with status Backup succeeded.

Validation Criteria:

# Should return object with populated Name and Type indicating the recovery point type:
az backup recoverypoint list --vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--container-name <container-name> \
--item-name nvt-vm-app01 \
--backup-management-type AzureIaasVM \
--query "[0].{Name:name,Type:properties.recoveryPointType}"

Step 68 β€” Configure Azure Site Recovery and perform failover​

Context: To ensure business continuity in case of regional disaster, VM nvt-vm-app01 should be replicated to Brazil South region using Azure Site Recovery.

Cost

Site Recovery generates ~USD 0.16/VM/h while active. Disable replication after validating the test failover.

Tasks:

[Portal] Configure replication in: Virtual machines β†’ nvt-vm-app01 β†’ Disaster recovery

ParameterValue
Target regionBrazil South
Target resource groupnvt-rg-staging
Recovery Services Vaultnvt-rsv-dr (create new in Brazil South)
Target VNetnvt-vnet-staging

Leave other settings as default and start replication.

[Portal] After initial replication completes, run test failover in: Recovery Services vaults β†’ nvt-rsv-dr β†’ Replicated items β†’ nvt-vm-app01 β†’ Test failover

  • Recovery point: Latest processed
  • Azure network: nvt-vnet-staging

[Portal] After validating test failover, run Cleanup test failover to remove the test VM created during the process.

Validation Criteria:

tip
  • Replicated item nvt-vm-app01 displays Replication health: Healthy and Status: Protected in DR vault.
  • Test failover completes without errors, creating temporary VM in Brazil South.
  • Cleanup removes temporary VM successfully and status returns to Protected.

Step 69 β€” Configure and interpret reports and alerts for backups​

Context: To close the backup governance cycle for NovaTech, the team should configure an alert for backup failures and verify compliance reports for vault nvt-rsv-main.

Tasks:

[Portal] Configure backup failure alert rule in: Recovery Services vaults β†’ nvt-rsv-main β†’ Alerts β†’ Create alert rule. Use signal Backup Jobs with condition Status = Failed. Link to Action Group nvt-ag-ops created in Step 62.

[Portal] Connect vault to workspace nvt-law-main in: Recovery Services vaults β†’ nvt-rsv-main β†’ Reports. Navigate to Backup center β†’ Reports and verify the Backup Summary dashboard.

[CLI] List all backup jobs from vault nvt-rsv-main in the last 24 hours:

az backup job list \
--vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--output table

Validation Criteria:

# Should return at least 1 line with Status = Completed referencing nvt-vm-app01:
az backup job list --vault-name nvt-rsv-main \
--resource-group nvt-rg-main \
--query "[?properties.status=='Completed'].{Job:name,VM:properties.entityFriendlyName,Status:properties.status}" \
--output table

# Should return array with name of the created alert rule:
az monitor activity-log alert list --resource-group nvt-rg-main \
--query "[?contains(name,'backup')].name"

Final Environment Validation​

Upon completing all 69 steps, run the commands below to confirm that the NovaTech environment is complete and all main components are in the expected state.

# Verify all resource groups created with tag NVT-CORE:
az group list --tag CostCenter=NVT-CORE \
--query "[].{Name:name,Location:location,Env:tags.Environment}" --output table

# Verify VMs in running state:
az vm list --resource-group nvt-rg-main --show-details \
--query "[].{Name:name,State:powerState,Zone:zones[0]}" --output table

# Verify storage accounts:
az storage account list --resource-group nvt-rg-main \
--query "[].{Name:name,SKU:sku.name,Encryption:encryption.keySource}" --output table

# Verify running containers:
az containerapp list --resource-group nvt-rg-main \
--query "[].{Name:name,Status:properties.runningStatus}" --output table

# Verify completed backup jobs:
az backup job list --vault-name nvt-rsv-main --resource-group nvt-rg-main \
--query "[?properties.status=='Completed'].{Job:name,Status:properties.status}" --output table
CategoryResourcesResource Group
Identity2 users, 2 groups, 1 guest, SSPRMicrosoft Entra ID
Governance1 policy, 1 lock, budget, 2 management groupsnvt-rg-main
Storagenvtprodsa (ZRS + CMK), nvtstagingsa (GRS)nvt-rg-main / nvt-rg-staging
Computenvt-vm-app01 (B2s), nvt-vm-app02 (B2s), nvt-vmss-procnvt-rg-main
Containersnvtregistry, nvt-aci-report, nvt-ca-apinvt-rg-main
App Servicenvt-webapp-portal (P1v3, slot staging)nvt-rg-main
Networknvt-vnet-main, nvt-vnet-staging, NSG, ASG, LB, Bastion, DNS, UDR, Private Endpointsnvt-rg-network
Monitoringnvt-law-main, alerts, Connection Monitor, VM Insightsnvt-rg-main
Backup and DRnvt-rsv-main, nvt-bv-main, Site Recovery (Brazil South)nvt-rg-main

AZ-104 Lab β€” NovaTech Solutions | Prefix: nvt | Mode: Azure CLI | 69 steps | 8 domains