This commit is contained in:
Toby
2026-03-24 18:02:50 -04:00
parent 2d209b9258
commit ba9e489584
111 changed files with 48382 additions and 3099 deletions
+429 -14
View File
@@ -9,50 +9,465 @@ metadata:
# ARM Templates & Bicep
Deploy Azure infrastructure with ARM templates and Bicep.
Deploy Azure infrastructure with ARM templates and Bicep. Bicep is the recommended domain-specific language that compiles to ARM JSON, offering cleaner syntax, modules, and first-class tooling support.
## Bicep Example
## When to Use
- You need Azure-native Infrastructure as Code without third-party tooling.
- Your organization standardizes on Azure and wants tight portal integration.
- You need What-If analysis before deploying changes.
- You are migrating existing ARM JSON templates to Bicep for maintainability.
- You need deployment scopes at resource group, subscription, management group, or tenant level.
## Prerequisites
```bash
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Install Bicep CLI (bundled with Azure CLI 2.20+)
az bicep install
az bicep upgrade
# Verify installation
az bicep version
# Login and set subscription
az login
az account set --subscription "my-subscription-id"
```
## Bicep Fundamentals
### Resource Group Deployment with Virtual Network
```bicep
// main.bicep
@description('Azure region for all resources')
param location string = resourceGroup().location
@description('Environment name used for resource naming')
@allowed(['dev', 'staging', 'prod'])
param environment string = 'dev'
@description('Base name for all resources')
param baseName string
var vnetName = '${baseName}-${environment}-vnet'
var nsgName = '${baseName}-${environment}-nsg'
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-05-01' = {
name: nsgName
location: location
properties: {
securityRules: [
{
name: 'AllowHTTPS'
properties: {
priority: 100
direction: 'Inbound'
access: 'Allow'
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRange: '443'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
}
}
{
name: 'DenyAllInbound'
properties: {
priority: 4096
direction: 'Inbound'
access: 'Deny'
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
}
}
]
}
}
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'web-subnet'
properties: {
addressPrefix: '10.0.1.0/24'
networkSecurityGroup: {
id: nsg.id
}
}
}
{
name: 'app-subnet'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
{
name: 'data-subnet'
properties: {
addressPrefix: '10.0.3.0/24'
privateEndpointNetworkPolicies: 'Enabled'
}
}
]
}
}
output vnetId string = vnet.id
output webSubnetId string = vnet.properties.subnets[0].id
output appSubnetId string = vnet.properties.subnets[1].id
```
### VM Deployment with Managed Identity
```bicep
// vm.bicep
param location string = resourceGroup().location
param vmName string
param subnetId string
param adminUsername string = 'azureuser'
resource vm 'Microsoft.Compute/virtualMachines@2023-03-01' = {
@secure()
param adminPublicKey string
resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = {
name: '${vmName}-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: subnetId
}
}
}
]
}
}
resource vm 'Microsoft.Compute/virtualMachines@2023-07-01' = {
name: vmName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
hardwareProfile: {
vmSize: 'Standard_B2s'
}
osProfile: {
computerName: vmName
adminUsername: 'azureuser'
adminUsername: adminUsername
linuxConfiguration: {
disablePasswordAuthentication: true
ssh: {
publicKeys: [
{
path: '/home/${adminUsername}/.ssh/authorized_keys'
keyData: adminPublicKey
}
]
}
}
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Premium_LRS'
}
}
}
networkProfile: {
networkInterfaces: [
{
id: nic.id
}
]
}
diagnosticsProfile: {
bootDiagnostics: {
enabled: true
}
}
}
}
output vmPrincipalId string = vm.identity.principalId
output vmId string = vm.id
```
## Deployment
## Bicep Modules
### Module Definition
```bicep
// modules/storage.bicep
@description('Storage account name (3-24 chars, lowercase alphanumeric)')
param storageAccountName string
param location string = resourceGroup().location
param sku string = 'Standard_LRS'
@allowed(['Hot', 'Cool', 'Archive'])
param accessTier string = 'Hot'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: sku
}
kind: 'StorageV2'
properties: {
accessTier: accessTier
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
output storageAccountId string = storageAccount.id
output primaryBlobEndpoint string = storageAccount.properties.primaryEndpoints.blob
```
### Consuming Modules
```bicep
// main.bicep
param location string = resourceGroup().location
param environment string = 'prod'
module storage 'modules/storage.bicep' = {
name: 'storage-deployment'
params: {
storageAccountName: 'myapp${environment}sa'
location: location
sku: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
}
}
module vnet 'modules/network.bicep' = {
name: 'vnet-deployment'
params: {
location: location
environment: environment
}
}
// Reference module outputs
output storageBlobEndpoint string = storage.outputs.primaryBlobEndpoint
```
## ARM JSON Template Structure
```json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "Name of the storage account"
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"variables": {
"storageSku": "Standard_LRS"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[variables('storageSku')]"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true,
"minimumTlsVersion": "TLS1_2"
}
}
],
"outputs": {
"storageId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
}
}
}
```
## Deployment Commands
```bash
# Deploy Bicep
# Validate a Bicep template before deployment
az deployment group validate \
--resource-group mygroup \
--template-file main.bicep \
--parameters environment='prod' baseName='myapp'
# Preview changes with What-If
az deployment group what-if \
--resource-group mygroup \
--template-file main.bicep \
--parameters environment='prod' baseName='myapp'
# Deploy Bicep to resource group
az deployment group create \
--resource-group mygroup \
--template-file main.bicep \
--parameters vmName=myvm
--parameters environment='prod' baseName='myapp' \
--name "deploy-$(date +%Y%m%d-%H%M%S)"
# Deploy ARM
# Deploy ARM JSON with parameter file
az deployment group create \
--resource-group mygroup \
--template-file template.json \
--parameters @parameters.json
--parameters @parameters.prod.json
# Subscription-level deployment (e.g., resource groups, policies)
az deployment sub create \
--location eastus \
--template-file subscription-level.bicep \
--parameters @params.json
# Management group deployment
az deployment mg create \
--management-group-id my-mg \
--location eastus \
--template-file mg-policy.bicep
# Export resource group to ARM JSON
az group export --name mygroup --output json > exported-template.json
# Decompile ARM JSON to Bicep
az bicep decompile --file exported-template.json
# Build Bicep to ARM JSON (for inspection)
az bicep build --file main.bicep --outfile main.json
# List deployments and their status
az deployment group list \
--resource-group mygroup \
--output table
# Delete a failed deployment
az deployment group delete \
--resource-group mygroup \
--name my-failed-deployment
```
## Best Practices
## Parameter Files
- Use Bicep over JSON ARM
- Implement modules for reusability
- Use parameter files per environment
- Validate before deployment
```json
// parameters.prod.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environment": { "value": "prod" },
"baseName": { "value": "myapp" },
"adminPublicKey": {
"reference": {
"keyVault": {
"id": "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault}"
},
"secretName": "ssh-public-key"
}
}
}
}
```
## Linked and Nested Templates
```bicep
// Deploy to a different resource group
module networkInSharedRg 'modules/network.bicep' = {
name: 'shared-network'
scope: resourceGroup('shared-networking-rg')
params: {
location: location
}
}
// Conditional deployment
param deployMonitoring bool = true
module monitoring 'modules/monitoring.bicep' = if (deployMonitoring) {
name: 'monitoring-deployment'
params: {
location: location
}
}
// Loop deployment
param storageAccounts array = [
{ name: 'logs', sku: 'Standard_LRS' }
{ name: 'data', sku: 'Standard_GRS' }
]
module storageLoop 'modules/storage.bicep' = [for account in storageAccounts: {
name: 'storage-${account.name}'
params: {
storageAccountName: '${baseName}${account.name}sa'
sku: account.sku
location: location
}
}]
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `InvalidTemplate` error | Syntax error in ARM JSON or Bicep | Run `az bicep build` to check for compile errors |
| `ResourceNotFound` during deployment | Resource dependency not declared | Add `dependsOn` or use implicit references in Bicep |
| `DeploymentFailed` with quota error | Subscription quota exceeded | Request quota increase or use a different region |
| `AuthorizationFailed` | Insufficient RBAC permissions | Assign Contributor role on the target resource group |
| Parameter file secrets in source control | Secrets stored as plain text | Use Key Vault references in parameter files |
| Deployment takes very long | Large number of resources deployed serially | Use `dependsOn` carefully to allow parallel deployment |
| `What-If` shows unexpected deletions | Complete mode instead of Incremental | Use `--mode Incremental` (the default) to avoid deleting unmanaged resources |
| Bicep module not found | Incorrect relative path | Verify path is relative to the consuming file |
## Related Skills
- `terraform-azure` -- Multi-cloud IaC alternative with broader provider support.
- `azure-networking` -- VNet, NSG, and firewall configurations referenced in templates.
- `azure-vms` -- Virtual machine sizing and configuration details.
- `azure-aks` -- Kubernetes cluster definitions for Bicep/ARM.
+374 -29
View File
@@ -9,54 +9,399 @@ metadata:
# Azure Kubernetes Service
Deploy managed Kubernetes clusters on Azure.
Deploy and manage production-grade Kubernetes clusters on Azure with AKS. Covers cluster creation, node pool management, networking, ingress controllers, monitoring, security, and Terraform-based provisioning.
## Create Cluster
## When to Use
- You need managed Kubernetes without maintaining control plane infrastructure.
- Your workloads require container orchestration with auto-scaling.
- You need tight integration with Azure AD, Key Vault, and Container Registry.
- You are running microservices that require service mesh, ingress, or network policies.
- You need GPU or spot node pools for specialized or cost-optimized workloads.
## Prerequisites
```bash
# Install Azure CLI and kubectl
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az aks install-cli
# Login and set subscription
az login
az account set --subscription "my-subscription-id"
# Register required providers
az provider register --namespace Microsoft.ContainerService
az provider register --namespace Microsoft.OperationsManagement
# Verify kubectl
kubectl version --client
```
## Cluster Creation
### Basic Production Cluster
```bash
# Create resource group
az group create --name myapp-rg --location eastus
# Create AKS cluster with best-practice defaults
az aks create \
--resource-group myapp-rg \
--name myapp-aks \
--node-count 3 \
--node-vm-size Standard_D4s_v5 \
--enable-managed-identity \
--enable-cluster-autoscaler \
--min-count 2 \
--max-count 10 \
--network-plugin azure \
--network-policy calico \
--service-cidr 10.1.0.0/16 \
--dns-service-ip 10.1.0.10 \
--vnet-subnet-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}" \
--enable-aad \
--aad-admin-group-object-ids "{aad-group-id}" \
--enable-azure-rbac \
--zones 1 2 3 \
--generate-ssh-keys \
--tags environment=prod team=platform
# Get cluster credentials
az aks get-credentials --resource-group myapp-rg --name myapp-aks
# Verify cluster access
kubectl get nodes -o wide
kubectl cluster-info
```
### Private Cluster
```bash
az aks create \
--resource-group mygroup \
--name myakscluster \
--resource-group myapp-rg \
--name myapp-private-aks \
--node-count 3 \
--node-vm-size Standard_B2s \
--node-vm-size Standard_D4s_v5 \
--enable-managed-identity \
--enable-private-cluster \
--private-dns-zone system \
--network-plugin azure \
--vnet-subnet-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}" \
--generate-ssh-keys
# Get credentials
az aks get-credentials --resource-group mygroup --name myakscluster
```
## Node Pools
## Node Pool Management
```bash
# Add a user node pool for application workloads
az aks nodepool add \
--resource-group mygroup \
--cluster-name myakscluster \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name apppool \
--node-count 3 \
--node-vm-size Standard_D8s_v5 \
--mode User \
--enable-cluster-autoscaler \
--min-count 2 \
--max-count 15 \
--zones 1 2 3 \
--labels workload=app tier=frontend \
--node-taints dedicated=app:NoSchedule \
--max-pods 50
# Add GPU node pool for ML workloads
az aks nodepool add \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name gpupool \
--node-count 1 \
--node-vm-size Standard_NC6
--node-vm-size Standard_NC6s_v3 \
--mode User \
--enable-cluster-autoscaler \
--min-count 0 \
--max-count 4 \
--node-taints sku=gpu:NoSchedule \
--labels workload=ml
# Add spot instance pool for batch workloads
az aks nodepool add \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name spotpool \
--node-count 2 \
--node-vm-size Standard_D4s_v5 \
--priority Spot \
--eviction-policy Delete \
--spot-max-price -1 \
--enable-cluster-autoscaler \
--min-count 0 \
--max-count 20 \
--labels workload=batch
# Scale a node pool manually
az aks nodepool scale \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name apppool \
--node-count 5
# Upgrade a node pool
az aks nodepool upgrade \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name apppool \
--kubernetes-version 1.28.3
# List node pools
az aks nodepool list \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--output table
```
## Enable Add-ons
## Ingress Controller Setup
```bash
# Enable monitoring
az aks enable-addons \
--resource-group mygroup \
--name myakscluster \
--addons monitoring
# Install NGINX ingress controller via Helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Enable Azure Policy
az aks enable-addons \
--resource-group mygroup \
--name myakscluster \
--addons azure-policy
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2 \
--set controller.nodeSelector."kubernetes\.io/os"=linux \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
--set controller.service.externalTrafficPolicy=Local
# Verify the ingress controller and get external IP
kubectl get svc -n ingress-nginx
```
## Best Practices
### Ingress Resource Example
- Use managed identity
- Enable Azure CNI for networking
- Implement pod identity
- Use node pools for workload isolation
- Enable cluster autoscaler
```yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: myapp
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
```
## Monitoring and Logging
```bash
# Enable Container Insights
az aks enable-addons \
--resource-group myapp-rg \
--name myapp-aks \
--addons monitoring \
--workspace-resource-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{workspace}"
# Enable Azure Policy add-on
az aks enable-addons \
--resource-group myapp-rg \
--name myapp-aks \
--addons azure-policy
# Enable Key Vault secrets provider
az aks enable-addons \
--resource-group myapp-rg \
--name myapp-aks \
--addons azure-keyvault-secrets-provider
# View cluster diagnostics
az aks show \
--resource-group myapp-rg \
--name myapp-aks \
--query "addonProfiles" \
--output table
# Install Prometheus + Grafana via Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kube-prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword='SecureGrafanaP@ss'
```
## ACR Integration
```bash
# Create Azure Container Registry
az acr create \
--resource-group myapp-rg \
--name myappacr \
--sku Standard
# Attach ACR to AKS (grants AcrPull role)
az aks update \
--resource-group myapp-rg \
--name myapp-aks \
--attach-acr myappacr
# Build and push image
az acr build \
--registry myappacr \
--image myapp:v1.0 \
--file Dockerfile .
# Verify pull access
kubectl run test --image=myappacr.azurecr.io/myapp:v1.0 --rm -it --restart=Never -- echo "ACR pull works"
```
## Terraform Configuration
```hcl
resource "azurerm_kubernetes_cluster" "aks" {
name = "myapp-aks"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = "myapp"
kubernetes_version = "1.28"
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 5
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
role = "system"
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "calico"
service_cidr = "10.1.0.0/16"
dns_service_ip = "10.1.0.10"
load_balancer_sku = "standard"
}
azure_active_directory_role_based_access_control {
managed = true
azure_rbac_enabled = true
admin_group_object_ids = [var.aks_admin_group_id]
}
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}
key_vault_secrets_provider {
secret_rotation_enabled = true
}
tags = var.tags
}
resource "azurerm_kubernetes_cluster_node_pool" "app" {
name = "app"
kubernetes_cluster_id = azurerm_kubernetes_cluster.aks.id
vm_size = "Standard_D8s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 15
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
workload = "app"
}
node_taints = [
"dedicated=app:NoSchedule"
]
tags = var.tags
}
```
## Cluster Upgrades
```bash
# Check available Kubernetes versions
az aks get-upgrades \
--resource-group myapp-rg \
--name myapp-aks \
--output table
# Upgrade control plane first
az aks upgrade \
--resource-group myapp-rg \
--name myapp-aks \
--kubernetes-version 1.28.3 \
--control-plane-only
# Then upgrade each node pool
az aks nodepool upgrade \
--resource-group myapp-rg \
--cluster-name myapp-aks \
--name apppool \
--kubernetes-version 1.28.3
# Check upgrade status
az aks show \
--resource-group myapp-rg \
--name myapp-aks \
--query "provisioningState"
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Nodes in `NotReady` state | VM resource exhaustion or network issues | Run `kubectl describe node <name>` and check events; scale up if needed |
| Pods stuck in `Pending` | No available nodes or resource requests too high | Check autoscaler status with `az aks show`; adjust resource requests |
| `ImagePullBackOff` error | ACR not attached or image tag wrong | Verify with `az aks check-acr --name myapp-aks --acr myappacr.azurecr.io` |
| Ingress returns 404 | Service or path mismatch in Ingress spec | Verify `kubectl get ingress` and service endpoints |
| Private cluster unreachable | No VPN or private endpoint configured | Use `az aks command invoke` or configure private DNS resolution |
| Cluster autoscaler not scaling | Pod resource requests not set | Define CPU/memory requests on all pods so the scheduler can calculate demand |
| Azure Policy violations blocking pods | Restrictive policies applied | Check `kubectl get constrainttemplate` and adjust policy assignments |
| Persistent volume not binding | StorageClass mismatch or zone issue | Verify `kubectl get pvc` and ensure StorageClass matches node pool zones |
## Related Skills
- `terraform-azure` -- Provision AKS clusters with Terraform for repeatable infrastructure.
- `azure-networking` -- VNet and subnet configuration required by Azure CNI.
- `arm-templates` -- Bicep-based AKS deployment as an alternative to Terraform.
- `azure-vms` -- Understanding VM sizes for node pool selection.
@@ -9,46 +9,412 @@ metadata:
# Azure Functions
Build serverless applications with Azure Functions.
Build and deploy serverless applications with Azure Functions. Covers function app creation, trigger and binding configuration, deployment strategies, real code examples in Python and Node.js, and production best practices.
## Create Function App
## When to Use
- You need event-driven compute that scales automatically to zero.
- You are building APIs, webhooks, or background processing pipelines.
- You want per-execution billing without managing servers.
- You need to respond to Azure service events (Blob Storage, Service Bus, Cosmos DB changes).
- You are implementing lightweight microservices or scheduled tasks.
## Prerequisites
```bash
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Install Azure Functions Core Tools v4
npm install -g azure-functions-core-tools@4
# Verify installation
func --version
# Login
az login
az account set --subscription "my-subscription-id"
# Create supporting resources
az group create --name functions-rg --location eastus
az storage account create \
--name myfuncstorageacct \
--resource-group functions-rg \
--location eastus \
--sku Standard_LRS
```
## Function App Creation
### Consumption Plan (Pay-per-execution)
```bash
# Python function app on Consumption plan
az functionapp create \
--resource-group mygroup \
--resource-group functions-rg \
--consumption-plan-location eastus \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--name myfunctionapp \
--storage-account mystorageaccount
--name myapp-func \
--storage-account myfuncstorageacct \
--os-type Linux
# Node.js function app
az functionapp create \
--resource-group functions-rg \
--consumption-plan-location eastus \
--runtime node \
--runtime-version 20 \
--functions-version 4 \
--name myapp-node-func \
--storage-account myfuncstorageacct \
--os-type Linux
```
## Function Code
### Premium Plan (VNet integration, no cold start)
```bash
# Create Premium plan
az functionapp plan create \
--resource-group functions-rg \
--name myapp-premium-plan \
--location eastus \
--sku EP1 \
--is-linux true
# Create function app on Premium plan
az functionapp create \
--resource-group functions-rg \
--plan myapp-premium-plan \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--name myapp-premium-func \
--storage-account myfuncstorageacct
```
## Trigger and Binding Examples
### HTTP Trigger -- Python
```python
# function_app.py (v2 programming model)
import azure.functions as func
import json
import logging
def main(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse("Hello, World!")
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
@app.route(route="users/{userId}", methods=["GET"])
def get_user(req: func.HttpRequest) -> func.HttpResponse:
user_id = req.route_params.get("userId")
logging.info(f"Fetching user: {user_id}")
if not user_id:
return func.HttpResponse(
json.dumps({"error": "userId is required"}),
status_code=400,
mimetype="application/json"
)
user = {"id": user_id, "name": "Jane Doe", "email": "jane@example.com"}
return func.HttpResponse(
json.dumps(user),
status_code=200,
mimetype="application/json"
)
@app.route(route="users", methods=["POST"])
def create_user(req: func.HttpRequest) -> func.HttpResponse:
try:
body = req.get_json()
except ValueError:
return func.HttpResponse(
json.dumps({"error": "Invalid JSON"}),
status_code=400,
mimetype="application/json"
)
logging.info(f"Creating user: {body.get('name')}")
return func.HttpResponse(
json.dumps({"id": "new-id", **body}),
status_code=201,
mimetype="application/json"
)
```
### HTTP Trigger -- Node.js
```javascript
// src/functions/httpTrigger.js (v4 programming model)
const { app } = require("@azure/functions");
app.http("getUser", {
methods: ["GET"],
authLevel: "function",
route: "users/{userId}",
handler: async (request, context) => {
const userId = request.params.userId;
context.log(`Fetching user: ${userId}`);
if (!userId) {
return { status: 400, jsonBody: { error: "userId is required" } };
}
const user = { id: userId, name: "Jane Doe", email: "jane@example.com" };
return { status: 200, jsonBody: user };
},
});
app.http("createUser", {
methods: ["POST"],
authLevel: "function",
route: "users",
handler: async (request, context) => {
const body = await request.json();
context.log(`Creating user: ${body.name}`);
return { status: 201, jsonBody: { id: "new-id", ...body } };
},
});
```
### Blob Trigger -- Python
```python
@app.blob_trigger(arg_name="blob", path="uploads/{name}",
connection="AzureWebJobsStorage")
def process_upload(blob: func.InputStream):
logging.info(f"Processing blob: {blob.name}, Size: {blob.length} bytes")
content = blob.read()
# Process file content here
```
### Timer Trigger -- Python
```python
@app.timer_trigger(schedule="0 */5 * * * *", arg_name="timer",
run_on_startup=False)
def cleanup_job(timer: func.TimerRequest):
if timer.past_due:
logging.warning("Timer is past due")
logging.info("Running scheduled cleanup")
# Cleanup logic here
```
### Service Bus Trigger -- Python
```python
@app.service_bus_queue_trigger(arg_name="msg", queue_name="orders",
connection="ServiceBusConnection")
@app.cosmos_db_output(arg_name="doc", database_name="mydb",
container_name="processed-orders",
connection="CosmosDBConnection")
def process_order(msg: func.ServiceBusMessage, doc: func.Out[func.Document]):
order = json.loads(msg.get_body().decode("utf-8"))
logging.info(f"Processing order: {order['id']}")
processed = {
"id": order["id"],
"status": "processed",
"items": order["items"],
"total": sum(item["price"] for item in order["items"])
}
doc.set(func.Document.from_dict(processed))
```
### Cosmos DB Change Feed Trigger -- Python
```python
@app.cosmos_db_trigger_v3(arg_name="documents", database_name="mydb",
container_name="orders",
connection="CosmosDBConnection",
lease_container_name="leases",
create_lease_container_if_not_exists=True)
def on_order_change(documents: func.DocumentList):
for doc in documents:
logging.info(f"Document changed: {doc['id']}")
```
## Local Development
```bash
# Initialize a new Python function project
func init MyFunctionProject --python
cd MyFunctionProject
# Create a new function from template
func new --name HttpExample --template "HTTP trigger" --authlevel function
# Run locally
func start
# Run locally with specific port
func start --port 7072
# Test locally
curl http://localhost:7071/api/HttpExample?name=World
```
## Deployment
```bash
# Deploy using Core Tools
func azure functionapp publish myfunctionapp
func azure functionapp publish myapp-func
# Deploy using ZIP
# Deploy with build step for Python
func azure functionapp publish myapp-func --build remote
# Deploy using ZIP package
zip -r function.zip . -x ".git/*" ".venv/*" "__pycache__/*"
az functionapp deployment source config-zip \
--resource-group mygroup \
--name myfunctionapp \
--resource-group functions-rg \
--name myapp-func \
--src function.zip
# Deploy via CI/CD with GitHub Actions
az functionapp deployment github-actions add \
--resource-group functions-rg \
--name myapp-func \
--repo "myorg/myrepo" \
--branch main \
--runtime python \
--login-with-github
```
## Best Practices
## Deployment Slots
- Use consumption plan for variable workloads
- Implement Durable Functions for orchestration
- Use managed identity for authentication
- Monitor with Application Insights
```bash
# Create a staging slot
az functionapp deployment slot create \
--resource-group functions-rg \
--name myapp-func \
--slot staging
# Deploy to staging slot
func azure functionapp publish myapp-func --slot staging
# Test staging slot
curl https://myapp-func-staging.azurewebsites.net/api/health
# Swap staging to production
az functionapp deployment slot swap \
--resource-group functions-rg \
--name myapp-func \
--slot staging \
--target-slot production
# Roll back by swapping again
az functionapp deployment slot swap \
--resource-group functions-rg \
--name myapp-func \
--slot staging \
--target-slot production
```
## Application Settings and Security
```bash
# Set application settings
az functionapp config appsettings set \
--resource-group functions-rg \
--name myapp-func \
--settings \
ServiceBusConnection="Endpoint=sb://..." \
CosmosDBConnection="AccountEndpoint=https://..." \
CUSTOM_SETTING="my-value"
# Set settings as slot-specific
az functionapp config appsettings set \
--resource-group functions-rg \
--name myapp-func \
--slot-settings \
ENVIRONMENT="staging"
# Enable managed identity
az functionapp identity assign \
--resource-group functions-rg \
--name myapp-func
# Configure CORS
az functionapp cors add \
--resource-group functions-rg \
--name myapp-func \
--allowed-origins "https://myapp.example.com"
# Set minimum TLS version
az functionapp config set \
--resource-group functions-rg \
--name myapp-func \
--min-tls-version 1.2
# Enable Application Insights
az functionapp config appsettings set \
--resource-group functions-rg \
--name myapp-func \
--settings APPINSIGHTS_INSTRUMENTATIONKEY="your-key"
```
## Terraform Configuration
```hcl
resource "azurerm_service_plan" "functions" {
name = "myapp-func-plan"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Linux"
sku_name = "Y1" # Consumption plan
}
resource "azurerm_linux_function_app" "main" {
name = "myapp-func"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
service_plan_id = azurerm_service_plan.functions.id
storage_account_name = azurerm_storage_account.func.name
storage_account_access_key = azurerm_storage_account.func.primary_access_key
identity {
type = "SystemAssigned"
}
site_config {
application_stack {
python_version = "3.11"
}
cors {
allowed_origins = ["https://myapp.example.com"]
}
}
app_settings = {
FUNCTIONS_WORKER_RUNTIME = "python"
WEBSITE_RUN_FROM_PACKAGE = "1"
APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.main.instrumentation_key
}
tags = var.tags
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Cold start latency > 10s | Consumption plan cold start | Use Premium plan (EP1+) or enable `WEBSITE_RUN_FROM_PACKAGE=1` |
| Function not triggering | Connection string misconfigured | Check `az functionapp config appsettings list` for correct binding values |
| `ModuleNotFoundError` in Python | Dependencies not installed during deploy | Use `--build remote` flag or include `requirements.txt` in package |
| HTTP 401 Unauthorized | Auth level mismatch or missing function key | Verify auth level in code matches expectations; pass `x-functions-key` header |
| Blob trigger not firing | Storage account connection wrong | Verify `AzureWebJobsStorage` points to the correct account |
| Timer trigger runs twice | Multiple instances on Premium plan | Set `WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT=1` or use singleton lock |
| Deployment slot swap fails | Slot settings not configured | Ensure slot-specific settings are marked with `--slot-settings` |
| Out of memory errors | Large payloads or memory leaks | Stream data instead of loading entirely; increase plan tier |
## Related Skills
- `azure-networking` -- VNet integration for Premium plan functions accessing private resources.
- `azure-sql` -- Database connections from function bindings.
- `terraform-azure` -- Infrastructure as Code for function app provisioning.
- `arm-templates` -- Bicep-based function app deployment.
@@ -9,52 +9,546 @@ metadata:
# Azure Networking
Design and implement Azure network infrastructure.
Design and implement Azure network infrastructure including VNets, subnets, NSGs, VNet peering, private endpoints, Azure Firewall, and Application Gateway. Covers both az CLI commands and Terraform configurations for production hub-spoke topologies.
## Create VNet
## When to Use
- You are designing the network foundation for Azure workloads.
- You need to isolate environments with VNets and NSGs.
- You are connecting on-premises networks to Azure via VPN or ExpressRoute.
- You need private connectivity to PaaS services via private endpoints.
- You are implementing centralized egress filtering with Azure Firewall.
- You need to set up load balancing or application-layer routing with Application Gateway.
## Prerequisites
```bash
az network vnet create \
--resource-group mygroup \
--name myvnet \
--address-prefix 10.0.0.0/16 \
--subnet-name default \
--subnet-prefix 10.0.1.0/24
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Login and set subscription
az login
az account set --subscription "my-subscription-id"
# Register required providers
az provider register --namespace Microsoft.Network
# Create resource group
az group create --name networking-rg --location eastus
```
## Network Security Group
## VNet and Subnet Creation
### Hub VNet
```bash
az network nsg create \
--resource-group mygroup \
--name mynsg
# Create hub VNet for shared services
az network vnet create \
--resource-group networking-rg \
--name hub-vnet \
--address-prefix 10.0.0.0/16 \
--location eastus \
--tags environment=prod role=hub
# Add subnets to hub
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name hub-vnet \
--name AzureFirewallSubnet \
--address-prefix 10.0.1.0/26
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name hub-vnet \
--name GatewaySubnet \
--address-prefix 10.0.2.0/27
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name hub-vnet \
--name SharedServicesSubnet \
--address-prefix 10.0.3.0/24
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name hub-vnet \
--name AzureBastionSubnet \
--address-prefix 10.0.4.0/26
```
### Spoke VNet
```bash
# Create spoke VNet for application workloads
az network vnet create \
--resource-group networking-rg \
--name spoke-prod-vnet \
--address-prefix 10.1.0.0/16 \
--location eastus \
--tags environment=prod role=spoke
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name web-subnet \
--address-prefix 10.1.1.0/24
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name app-subnet \
--address-prefix 10.1.2.0/24
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name data-subnet \
--address-prefix 10.1.3.0/24 \
--private-endpoint-network-policies Enabled
# List all subnets in a VNet
az network vnet subnet list \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--output table
```
## Network Security Groups
```bash
# Create NSG for web tier
az network nsg create \
--resource-group networking-rg \
--name web-nsg \
--tags tier=web
# Allow HTTPS from internet
az network nsg rule create \
--resource-group mygroup \
--nsg-name mynsg \
--resource-group networking-rg \
--nsg-name web-nsg \
--name AllowHTTPS \
--priority 100 \
--destination-port-ranges 443 \
--access Allow
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes Internet \
--destination-port-ranges 443
# Allow HTTP for redirect
az network nsg rule create \
--resource-group networking-rg \
--nsg-name web-nsg \
--name AllowHTTP \
--priority 110 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes Internet \
--destination-port-ranges 80
# Deny all other inbound traffic
az network nsg rule create \
--resource-group networking-rg \
--nsg-name web-nsg \
--name DenyAllInbound \
--priority 4096 \
--direction Inbound \
--access Deny \
--protocol '*' \
--source-address-prefixes '*' \
--destination-port-ranges '*'
# Create NSG for app tier -- only allow from web subnet
az network nsg create \
--resource-group networking-rg \
--name app-nsg
az network nsg rule create \
--resource-group networking-rg \
--nsg-name app-nsg \
--name AllowFromWeb \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.1.1.0/24 \
--destination-port-ranges 8080
# Create NSG for data tier -- only allow from app subnet
az network nsg create \
--resource-group networking-rg \
--name data-nsg
az network nsg rule create \
--resource-group networking-rg \
--nsg-name data-nsg \
--name AllowSQLFromApp \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.1.2.0/24 \
--destination-port-ranges 1433
# Associate NSG with subnet
az network vnet subnet update \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name web-subnet \
--network-security-group web-nsg
az network vnet subnet update \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name app-subnet \
--network-security-group app-nsg
az network vnet subnet update \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name data-subnet \
--network-security-group data-nsg
# View effective NSG rules
az network nic list-effective-nsg \
--resource-group networking-rg \
--name myvm-nic \
--output table
```
## Private Endpoint
## VNet Peering
```bash
az network private-endpoint create \
--resource-group mygroup \
--name myendpoint \
--vnet-name myvnet \
--subnet default \
--private-connection-resource-id /subscriptions/.../sql/... \
--group-id sqlServer \
--connection-name myconnection
# Peer hub to spoke
az network vnet peering create \
--resource-group networking-rg \
--name hub-to-spoke-prod \
--vnet-name hub-vnet \
--remote-vnet spoke-prod-vnet \
--allow-vnet-access \
--allow-forwarded-traffic \
--allow-gateway-transit
# Peer spoke to hub
az network vnet peering create \
--resource-group networking-rg \
--name spoke-prod-to-hub \
--vnet-name spoke-prod-vnet \
--remote-vnet hub-vnet \
--allow-vnet-access \
--allow-forwarded-traffic \
--use-remote-gateways false
# Verify peering status
az network vnet peering list \
--resource-group networking-rg \
--vnet-name hub-vnet \
--output table
```
## Best Practices
## Private Endpoints
- Implement hub-spoke topology
- Use NSGs and Azure Firewall
- Enable DDoS protection
- Use private endpoints
- Implement VNet peering
```bash
# Create private endpoint for Azure SQL
az network private-endpoint create \
--resource-group networking-rg \
--name sql-private-endpoint \
--vnet-name spoke-prod-vnet \
--subnet data-subnet \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Sql/servers/myserver" \
--group-id sqlServer \
--connection-name sql-connection
# Create private DNS zone for SQL
az network private-dns zone create \
--resource-group networking-rg \
--name privatelink.database.windows.net
# Link DNS zone to VNet
az network private-dns link vnet create \
--resource-group networking-rg \
--zone-name privatelink.database.windows.net \
--name spoke-dns-link \
--virtual-network spoke-prod-vnet \
--registration-enabled false
# Create DNS record for the private endpoint
az network private-endpoint dns-zone-group create \
--resource-group networking-rg \
--endpoint-name sql-private-endpoint \
--name sql-dns-group \
--private-dns-zone privatelink.database.windows.net \
--zone-name privatelink.database.windows.net
# Create private endpoint for Storage Account
az network private-endpoint create \
--resource-group networking-rg \
--name storage-private-endpoint \
--vnet-name spoke-prod-vnet \
--subnet data-subnet \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/mystorageacct" \
--group-id blob \
--connection-name storage-blob-connection
# Create private endpoint for Key Vault
az network private-endpoint create \
--resource-group networking-rg \
--name kv-private-endpoint \
--vnet-name spoke-prod-vnet \
--subnet app-subnet \
--private-connection-resource-id "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/myvault" \
--group-id vault \
--connection-name kv-connection
```
## Azure Firewall
```bash
# Create public IP for firewall
az network public-ip create \
--resource-group networking-rg \
--name fw-public-ip \
--sku Standard \
--allocation-method Static
# Create Azure Firewall
az network firewall create \
--resource-group networking-rg \
--name hub-firewall \
--location eastus \
--sku AZFW_VNet \
--tier Standard
# Configure firewall IP
az network firewall ip-config create \
--resource-group networking-rg \
--firewall-name hub-firewall \
--name fw-ipconfig \
--public-ip-address fw-public-ip \
--vnet-name hub-vnet
# Get firewall private IP for route tables
FW_PRIVATE_IP=$(az network firewall show \
--resource-group networking-rg \
--name hub-firewall \
--query "ipConfigurations[0].privateIpAddress" \
--output tsv)
# Create application rule allowing web traffic
az network firewall application-rule create \
--resource-group networking-rg \
--firewall-name hub-firewall \
--collection-name AllowWeb \
--name AllowGoogle \
--protocols Https=443 Http=80 \
--source-addresses 10.1.0.0/16 \
--target-fqdns "*.google.com" "*.microsoft.com" \
--action Allow \
--priority 100
# Create network rule for DNS
az network firewall network-rule create \
--resource-group networking-rg \
--firewall-name hub-firewall \
--collection-name AllowDNS \
--name AllowDNS \
--protocols UDP \
--source-addresses 10.1.0.0/16 \
--destination-addresses 168.63.129.16 \
--destination-ports 53 \
--action Allow \
--priority 200
# Create route table to send traffic through firewall
az network route-table create \
--resource-group networking-rg \
--name spoke-route-table
az network route-table route create \
--resource-group networking-rg \
--route-table-name spoke-route-table \
--name default-to-firewall \
--address-prefix 0.0.0.0/0 \
--next-hop-type VirtualAppliance \
--next-hop-ip-address "$FW_PRIVATE_IP"
# Associate route table with spoke subnet
az network vnet subnet update \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name app-subnet \
--route-table spoke-route-table
```
## Application Gateway with WAF
```bash
# Create public IP
az network public-ip create \
--resource-group networking-rg \
--name appgw-public-ip \
--sku Standard \
--allocation-method Static
# Create Application Gateway subnet
az network vnet subnet create \
--resource-group networking-rg \
--vnet-name spoke-prod-vnet \
--name AppGatewaySubnet \
--address-prefix 10.1.10.0/24
# Create Application Gateway with WAF v2
az network application-gateway create \
--resource-group networking-rg \
--name myapp-appgw \
--location eastus \
--sku WAF_v2 \
--capacity 2 \
--vnet-name spoke-prod-vnet \
--subnet AppGatewaySubnet \
--public-ip-address appgw-public-ip \
--http-settings-port 80 \
--http-settings-protocol Http \
--frontend-port 443 \
--servers 10.1.2.4 10.1.2.5
# Enable WAF policy
az network application-gateway waf-policy create \
--resource-group networking-rg \
--name myapp-waf-policy
az network application-gateway waf-policy managed-rule rule-set add \
--resource-group networking-rg \
--policy-name myapp-waf-policy \
--type OWASP \
--version 3.2
```
## Terraform Configuration
```hcl
resource "azurerm_virtual_network" "hub" {
name = "hub-vnet"
location = azurerm_resource_group.networking.location
resource_group_name = azurerm_resource_group.networking.name
address_space = ["10.0.0.0/16"]
tags = var.tags
}
resource "azurerm_subnet" "firewall" {
name = "AzureFirewallSubnet"
resource_group_name = azurerm_resource_group.networking.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.0.1.0/26"]
}
resource "azurerm_virtual_network" "spoke" {
name = "spoke-prod-vnet"
location = azurerm_resource_group.networking.location
resource_group_name = azurerm_resource_group.networking.name
address_space = ["10.1.0.0/16"]
tags = var.tags
}
resource "azurerm_subnet" "web" {
name = "web-subnet"
resource_group_name = azurerm_resource_group.networking.name
virtual_network_name = azurerm_virtual_network.spoke.name
address_prefixes = ["10.1.1.0/24"]
}
resource "azurerm_network_security_group" "web" {
name = "web-nsg"
location = azurerm_resource_group.networking.location
resource_group_name = azurerm_resource_group.networking.name
security_rule {
name = "AllowHTTPS"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
}
security_rule {
name = "DenyAllInbound"
priority = 4096
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = var.tags
}
resource "azurerm_subnet_network_security_group_association" "web" {
subnet_id = azurerm_subnet.web.id
network_security_group_id = azurerm_network_security_group.web.id
}
resource "azurerm_virtual_network_peering" "hub_to_spoke" {
name = "hub-to-spoke"
resource_group_name = azurerm_resource_group.networking.name
virtual_network_name = azurerm_virtual_network.hub.name
remote_virtual_network_id = azurerm_virtual_network.spoke.id
allow_forwarded_traffic = true
allow_gateway_transit = true
}
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "spoke-to-hub"
resource_group_name = azurerm_resource_group.networking.name
virtual_network_name = azurerm_virtual_network.spoke.name
remote_virtual_network_id = azurerm_virtual_network.hub.id
allow_forwarded_traffic = true
use_remote_gateways = false
}
resource "azurerm_private_endpoint" "sql" {
name = "sql-private-endpoint"
location = azurerm_resource_group.networking.location
resource_group_name = azurerm_resource_group.networking.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "sql-connection"
private_connection_resource_id = azurerm_mssql_server.main.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
private_dns_zone_group {
name = "sql-dns-group"
private_dns_zone_ids = [azurerm_private_dns_zone.sql.id]
}
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| VMs cannot reach the internet | NSG blocking outbound or missing route | Check NSG rules with `az network nic list-effective-nsg`; verify route table |
| VNet peering shows `Disconnected` | Peering created in one direction only | Create peering from both sides (hub-to-spoke AND spoke-to-hub) |
| Private endpoint DNS not resolving | Private DNS zone not linked to VNet | Link DNS zone with `az network private-dns link vnet create` |
| NSG rule not taking effect | Higher-priority rule overriding | List rules with `az network nsg rule list` and check priority ordering |
| Application Gateway health probes failing | Backend pool servers unreachable | Verify NSG allows traffic from the AppGateway subnet |
| Azure Firewall blocking legitimate traffic | Missing application or network rule | Check firewall logs in Log Analytics; add appropriate rule |
| Cross-VNet communication failing | Peering not configured or route missing | Verify peering status and that `allow-vnet-access` is enabled |
| High latency between regions | Traffic routing through unexpected path | Use `az network watcher next-hop` to diagnose routing |
## Related Skills
- `azure-vms` -- VM network interface and NSG configuration.
- `azure-aks` -- AKS VNet integration with Azure CNI.
- `azure-sql` -- Private endpoint configuration for database access.
- `terraform-azure` -- Network infrastructure provisioning with Terraform.
- `azure-functions` -- VNet integration for Premium plan functions.
+466 -27
View File
@@ -9,50 +9,489 @@ metadata:
# Azure SQL
Deploy managed databases on Azure.
Deploy and manage Azure SQL Database, Elastic Pools, and Cosmos DB. Covers server provisioning, firewall rules, geo-replication, backup strategies, performance tuning, security hardening, and Terraform configurations.
## Create SQL Database
## When to Use
- You need a fully managed relational database on Azure.
- Your application requires geo-replication for disaster recovery.
- You need elastic scaling across multiple databases with Elastic Pools.
- You are migrating on-premises SQL Server workloads to the cloud.
- You need a globally distributed NoSQL database (Cosmos DB).
## Prerequisites
```bash
# Create server
az sql server create \
--name myserver \
--resource-group mygroup \
--admin-user sqladmin \
--admin-password SecureP@ss123
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Create database
az sql db create \
--resource-group mygroup \
--server myserver \
--name mydb \
--service-objective S1
# Login and set subscription
az login
az account set --subscription "my-subscription-id"
# Create resource group
az group create --name database-rg --location eastus
```
## Firewall Rules
## SQL Server and Database Creation
### Create SQL Server
```bash
# Create logical SQL server
az sql server create \
--resource-group database-rg \
--name myapp-sqlserver \
--location eastus \
--admin-user sqladmin \
--admin-password 'S3cur3P@ssw0rd!' \
--enable-public-network false \
--minimal-tls-version 1.2
# Enable Azure AD authentication
az sql server ad-admin create \
--resource-group database-rg \
--server-name myapp-sqlserver \
--display-name "SQL Admins" \
--object-id "{aad-group-object-id}"
# Enable Azure AD only authentication (disable SQL auth)
az sql server ad-only-auth enable \
--resource-group database-rg \
--name myapp-sqlserver
```
### Create Databases
```bash
# Create General Purpose database
az sql db create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-db \
--edition GeneralPurpose \
--compute-model Serverless \
--auto-pause-delay 60 \
--min-capacity 0.5 \
--max-size 32GB \
--backup-storage-redundancy Geo \
--zone-redundant false
# Create Business Critical database for production
az sql db create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--edition BusinessCritical \
--service-objective BC_Gen5_4 \
--max-size 256GB \
--backup-storage-redundancy Geo \
--zone-redundant true \
--read-scale Enabled
# Create Hyperscale database for large workloads
az sql db create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-analytics-db \
--edition Hyperscale \
--service-objective HS_Gen5_4 \
--ha-replicas 2
# List databases on server
az sql db list \
--resource-group database-rg \
--server myapp-sqlserver \
--output table
```
### Elastic Pools
```bash
# Create elastic pool
az sql elastic-pool create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-pool \
--edition GeneralPurpose \
--capacity 4 \
--db-max-capacity 2 \
--db-min-capacity 0.25 \
--max-size 256GB \
--zone-redundant false
# Move database into elastic pool
az sql db update \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-db \
--elastic-pool myapp-pool
# Monitor elastic pool usage
az sql elastic-pool list-dbs \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-pool \
--output table
```
## Firewall Rules and Network Security
```bash
# Allow Azure services
az sql server firewall-rule create \
--resource-group mygroup \
--server myserver \
--name AllowAzure \
--resource-group database-rg \
--server myapp-sqlserver \
--name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
# Allow specific IP range (office network)
az sql server firewall-rule create \
--resource-group database-rg \
--server myapp-sqlserver \
--name AllowOffice \
--start-ip-address 203.0.113.0 \
--end-ip-address 203.0.113.255
# Allow your current client IP
az sql server firewall-rule create \
--resource-group database-rg \
--server myapp-sqlserver \
--name AllowMyIP \
--start-ip-address "$(curl -s ifconfig.me)" \
--end-ip-address "$(curl -s ifconfig.me)"
# Create VNet rule for subnet access
az sql server vnet-rule create \
--resource-group database-rg \
--server myapp-sqlserver \
--name AllowAppSubnet \
--vnet-name spoke-prod-vnet \
--subnet app-subnet
# List firewall rules
az sql server firewall-rule list \
--resource-group database-rg \
--server myapp-sqlserver \
--output table
# Remove a firewall rule
az sql server firewall-rule delete \
--resource-group database-rg \
--server myapp-sqlserver \
--name AllowMyIP
```
## Geo-Replication and Failover
```bash
# Create failover group with secondary server
az sql server create \
--resource-group database-rg \
--name myapp-sqlserver-secondary \
--location westus \
--admin-user sqladmin \
--admin-password 'S3cur3P@ssw0rd!'
az sql failover-group create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-failover-group \
--partner-server myapp-sqlserver-secondary \
--partner-resource-group database-rg \
--failover-policy Automatic \
--grace-period 1 \
--add-db myapp-prod-db
# Check failover group status
az sql failover-group show \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-failover-group \
--output table
# Manual failover (for testing or planned maintenance)
az sql failover-group set-primary \
--resource-group database-rg \
--server myapp-sqlserver-secondary \
--name myapp-failover-group
# Create active geo-replication (without failover group)
az sql db replica create \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-db \
--partner-server myapp-sqlserver-secondary \
--partner-resource-group database-rg
```
## Backup and Restore
```bash
# Configure short-term retention (1-35 days)
az sql db str-policy set \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--retention-days 14 \
--diffbackup-hours 12
# Configure long-term retention
az sql db ltr-policy set \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--weekly-retention P4W \
--monthly-retention P12M \
--yearly-retention P5Y \
--week-of-year 1
# Restore database to a point in time
az sql db restore \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-db-restored \
--dest-name myapp-db-restored \
--time "2026-03-23T10:00:00Z"
# Restore from long-term backup
az sql db ltr-backup list \
--resource-group database-rg \
--server myapp-sqlserver \
--database myapp-prod-db \
--output table
# Export database to bacpac
az sql db export \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-db \
--admin-user sqladmin \
--admin-password 'S3cur3P@ssw0rd!' \
--storage-key-type StorageAccessKey \
--storage-key "{storage-account-key}" \
--storage-uri "https://mystorageacct.blob.core.windows.net/backups/myapp-db.bacpac"
```
## Performance Tuning
```bash
# Enable automatic tuning
az sql db update \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--set tags.autoTuning=enabled
# Check database performance recommendations
az sql db advisor list \
--resource-group database-rg \
--server myapp-sqlserver \
--database myapp-prod-db \
--output table
# Scale database tier
az sql db update \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--service-objective BC_Gen5_8
# Enable Query Store (via SQL)
# sqlcmd -S myapp-sqlserver.database.windows.net -d myapp-prod-db -Q "ALTER DATABASE [myapp-prod-db] SET QUERY_STORE = ON"
# View DTU/vCore usage metrics
az monitor metrics list \
--resource "/subscriptions/{sub}/resourceGroups/database-rg/providers/Microsoft.Sql/servers/myapp-sqlserver/databases/myapp-prod-db" \
--metric "cpu_percent" "dtu_consumption_percent" "storage_percent" \
--interval PT1H \
--output table
```
## Security Hardening
```bash
# Enable Advanced Threat Protection
az sql db threat-policy update \
--resource-group database-rg \
--server myapp-sqlserver \
--name myapp-prod-db \
--state Enabled \
--email-addresses security@example.com \
--email-account-admins true
# Enable auditing to storage
az sql server audit-policy update \
--resource-group database-rg \
--name myapp-sqlserver \
--state Enabled \
--storage-account mystorageacct \
--retention-days 90
# Enable auditing to Log Analytics
az sql server audit-policy update \
--resource-group database-rg \
--name myapp-sqlserver \
--state Enabled \
--lats Enabled \
--lawri "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{workspace}"
# Enable Transparent Data Encryption (enabled by default)
az sql db tde set \
--resource-group database-rg \
--server myapp-sqlserver \
--database myapp-prod-db \
--status Enabled
# Enable vulnerability assessment
az sql vm update \
--resource-group database-rg \
--name myapp-sqlserver
```
## Cosmos DB
```bash
# Create Cosmos DB account with SQL API
az cosmosdb create \
--name mycosmosdb \
--resource-group mygroup \
--default-consistency-level Session
--resource-group database-rg \
--name myapp-cosmos \
--default-consistency-level Session \
--locations regionName=eastus failoverPriority=0 isZoneRedundant=true \
--locations regionName=westus failoverPriority=1 isZoneRedundant=false \
--enable-automatic-failover true \
--enable-multiple-write-locations false
# Create database
az cosmosdb sql database create \
--resource-group database-rg \
--account-name myapp-cosmos \
--name myappdb \
--throughput 400
# Create container with partition key
az cosmosdb sql container create \
--resource-group database-rg \
--account-name myapp-cosmos \
--database-name myappdb \
--name orders \
--partition-key-path "/customerId" \
--throughput 400 \
--idx @indexing-policy.json
# Enable autoscale throughput
az cosmosdb sql container throughput update \
--resource-group database-rg \
--account-name myapp-cosmos \
--database-name myappdb \
--name orders \
--max-throughput 4000
```
## Best Practices
## Terraform Configuration
- Enable transparent data encryption
- Use Azure AD authentication
- Implement geo-replication
- Configure automated backups
- Use private endpoints
```hcl
resource "azurerm_mssql_server" "main" {
name = "myapp-sqlserver"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = "sqladmin"
administrator_login_password = var.sql_admin_password
minimum_tls_version = "1.2"
public_network_access_enabled = false
azuread_administrator {
login_username = "SQL Admins"
object_id = var.sql_admin_aad_group_id
}
tags = var.tags
}
resource "azurerm_mssql_database" "main" {
name = "myapp-prod-db"
server_id = azurerm_mssql_server.main.id
collation = "SQL_Latin1_General_CP1_CI_AS"
license_type = "LicenseIncluded"
sku_name = "BC_Gen5_4"
max_size_gb = 256
zone_redundant = true
read_scale = true
short_term_retention_policy {
retention_days = 14
backup_interval_in_hours = 12
}
long_term_retention_policy {
weekly_retention = "P4W"
monthly_retention = "P12M"
yearly_retention = "P5Y"
week_of_year = 1
}
threat_detection_policy {
state = "Enabled"
email_addresses = ["security@example.com"]
email_account_admins = "Enabled"
retention_days = 90
storage_endpoint = azurerm_storage_account.audit.primary_blob_endpoint
storage_account_access_key = azurerm_storage_account.audit.primary_access_key
}
tags = var.tags
}
resource "azurerm_mssql_failover_group" "main" {
name = "myapp-failover-group"
server_id = azurerm_mssql_server.main.id
databases = [azurerm_mssql_database.main.id]
partner_server {
id = azurerm_mssql_server.secondary.id
}
read_write_endpoint_failover_policy {
mode = "Automatic"
grace_minutes = 60
}
tags = var.tags
}
resource "azurerm_private_endpoint" "sql" {
name = "sql-private-endpoint"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "sql-connection"
private_connection_resource_id = azurerm_mssql_server.main.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Cannot connect to SQL server | Firewall rule missing or public access disabled | Add client IP with `az sql server firewall-rule create` or use private endpoint |
| Login failed for user | Incorrect credentials or Azure AD not configured | Verify admin credentials; enable Azure AD auth on the server |
| Database DTU at 100% | Under-provisioned tier or inefficient queries | Scale up service objective; review Query Store for expensive queries |
| Geo-replication lag is high | Large transaction volumes or network latency | Monitor with `sys.dm_geo_replication_link_status`; consider Hyperscale |
| Point-in-time restore fails | Requested time is outside retention window | Check retention policy; use long-term backups for older data |
| Elastic pool running out of eDTUs | Too many active databases in pool | Increase pool capacity or move heavy databases to dedicated tier |
| TDE key rotation failure | Key Vault access policy missing | Grant SQL server managed identity GET, WRAP, UNWRAP permissions |
| Connection timeout from app | Network path blocked or DNS issue | Use `az network watcher test-connectivity`; verify private DNS resolution |
## Related Skills
- `azure-networking` -- Private endpoints and VNet rules for SQL access.
- `azure-functions` -- SQL bindings for serverless data access.
- `terraform-azure` -- Terraform-based SQL infrastructure provisioning.
- `arm-templates` -- Bicep templates for SQL deployments.
+481 -19
View File
@@ -9,37 +9,499 @@ metadata:
# Azure Virtual Machines
Deploy and manage Azure VMs and scale sets.
Deploy and manage Azure VMs, availability sets, scale sets, custom images, and managed disks. Covers VM creation, sizing, disk management, auto-scaling, and Terraform configurations for production environments.
## Create VM
## When to Use
- You need full control over the operating system and runtime environment.
- Your application requires specific OS configurations or kernel modules.
- You are running legacy applications that cannot be containerized.
- You need GPU-accelerated compute for ML training or rendering.
- You need high-availability compute with availability zones or scale sets.
## Prerequisites
```bash
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Login and set subscription
az login
az account set --subscription "my-subscription-id"
# Create resource group
az group create --name compute-rg --location eastus
# List available VM sizes in a region
az vm list-sizes --location eastus --output table
# List available VM images
az vm image list --output table
az vm image list --publisher Canonical --offer 0001-com-ubuntu-server-jammy --all --output table
```
## VM Creation
### Linux VM with SSH Key
```bash
az vm create \
--resource-group mygroup \
--name myvm \
--resource-group compute-rg \
--name myapp-vm \
--image Ubuntu2204 \
--size Standard_D4s_v5 \
--admin-username azureuser \
--generate-ssh-keys \
--vnet-name myapp-vnet \
--subnet app-subnet \
--nsg "" \
--public-ip-address "" \
--os-disk-size-gb 64 \
--os-disk-caching ReadWrite \
--storage-sku Premium_LRS \
--zone 1 \
--assign-identity \
--tags environment=prod team=platform app=myapp
# SSH into the VM (if public IP assigned)
ssh azureuser@$(az vm show -g compute-rg -n myapp-vm -d --query publicIps -o tsv)
```
### Windows VM
```bash
az vm create \
--resource-group compute-rg \
--name myapp-win-vm \
--image Win2022Datacenter \
--size Standard_D4s_v5 \
--admin-username azureadmin \
--admin-password 'S3cur3P@ssw0rd!' \
--vnet-name myapp-vnet \
--subnet app-subnet \
--public-ip-address "" \
--os-disk-size-gb 128 \
--storage-sku Premium_LRS \
--zone 1
```
### VM with Cloud-Init
```bash
# cloud-init.yaml
# #cloud-config
# package_update: true
# packages:
# - nginx
# - docker.io
# runcmd:
# - systemctl enable nginx
# - systemctl start nginx
# - usermod -aG docker azureuser
az vm create \
--resource-group compute-rg \
--name web-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--nsg-rule SSH
--custom-data cloud-init.yaml \
--tags role=web
```
## Scale Sets
## VM Size Guide
| Family | Example Sizes | Use Case |
|--------|---------------|----------|
| B-series | Standard_B1s, Standard_B2s | Dev/test, low-traffic web servers |
| D-series | Standard_D4s_v5, Standard_D8s_v5 | General purpose, most production workloads |
| E-series | Standard_E4s_v5, Standard_E16s_v5 | Memory-intensive (databases, caching) |
| F-series | Standard_F4s_v2, Standard_F16s_v2 | CPU-intensive (batch processing, analytics) |
| L-series | Standard_L8s_v3, Standard_L32s_v3 | Storage-optimized (big data, SQL) |
| N-series | Standard_NC6s_v3, Standard_NC24ads_A100_v4 | GPU workloads (ML training, rendering) |
| M-series | Standard_M128s | SAP HANA, large in-memory workloads |
```bash
az vmss create \
--resource-group mygroup \
--name myvmss \
--image Ubuntu2204 \
--instance-count 2 \
--vm-sku Standard_B2s \
--upgrade-policy-mode automatic
# Find VM sizes with specific capabilities
az vm list-sizes --location eastus \
--query "[?numberOfCores >= \`4\` && memoryInMb >= \`16000\`]" \
--output table
# Check VM size availability in a zone
az vm list-skus --location eastus \
--size Standard_D4s_v5 \
--output table
```
## Best Practices
## Managed Disks
- Use managed disks
- Implement availability zones
- Use scale sets for auto-scaling
- Enable Azure Backup
- Use spot instances for cost savings
```bash
# Add a data disk to existing VM
az vm disk attach \
--resource-group compute-rg \
--vm-name myapp-vm \
--name myapp-data-disk \
--size-gb 256 \
--sku Premium_LRS \
--new \
--lun 0
# Create a standalone managed disk
az disk create \
--resource-group compute-rg \
--name shared-data-disk \
--size-gb 512 \
--sku Premium_LRS \
--zone 1
# Resize a disk (VM must be deallocated)
az vm deallocate --resource-group compute-rg --name myapp-vm
az disk update \
--resource-group compute-rg \
--name myapp-data-disk \
--size-gb 512
az vm start --resource-group compute-rg --name myapp-vm
# Snapshot a disk for backup
az snapshot create \
--resource-group compute-rg \
--name myapp-disk-snapshot \
--source myapp-data-disk
# Create disk from snapshot
az disk create \
--resource-group compute-rg \
--name myapp-disk-from-snap \
--source myapp-disk-snapshot \
--sku Premium_LRS
# List disks attached to a VM
az vm show \
--resource-group compute-rg \
--name myapp-vm \
--query "storageProfile.dataDisks" \
--output table
```
## Custom Images
```bash
# Generalize the VM (run inside the VM first)
# sudo waagent -deprovision+user -force
# Deallocate and generalize
az vm deallocate --resource-group compute-rg --name myapp-vm
az vm generalize --resource-group compute-rg --name myapp-vm
# Create image from VM
az image create \
--resource-group compute-rg \
--name myapp-golden-image \
--source myapp-vm \
--os-type Linux
# Create VM from custom image
az vm create \
--resource-group compute-rg \
--name myapp-from-image \
--image myapp-golden-image \
--size Standard_D4s_v5 \
--admin-username azureuser \
--generate-ssh-keys
# Use Azure Compute Gallery for shared images
az sig create \
--resource-group compute-rg \
--gallery-name myAppGallery
az sig image-definition create \
--resource-group compute-rg \
--gallery-name myAppGallery \
--gallery-image-definition myapp-image \
--publisher myorg \
--offer myapp \
--sku 1.0 \
--os-type Linux \
--os-state Generalized
az sig image-version create \
--resource-group compute-rg \
--gallery-name myAppGallery \
--gallery-image-definition myapp-image \
--gallery-image-version 1.0.0 \
--managed-image myapp-golden-image \
--target-regions eastus westus \
--replica-count 2
```
## Availability Sets and Zones
```bash
# Create availability set
az vm availability-set create \
--resource-group compute-rg \
--name myapp-avset \
--platform-fault-domain-count 3 \
--platform-update-domain-count 5
# Create VM in availability set
az vm create \
--resource-group compute-rg \
--name myapp-vm-1 \
--image Ubuntu2204 \
--size Standard_D4s_v5 \
--availability-set myapp-avset \
--admin-username azureuser \
--generate-ssh-keys
# Create VMs across availability zones
for zone in 1 2 3; do
az vm create \
--resource-group compute-rg \
--name "myapp-vm-zone${zone}" \
--image Ubuntu2204 \
--size Standard_D4s_v5 \
--zone "$zone" \
--admin-username azureuser \
--generate-ssh-keys \
--no-wait
done
```
## Virtual Machine Scale Sets
```bash
# Create VMSS with autoscaling
az vmss create \
--resource-group compute-rg \
--name myapp-vmss \
--image Ubuntu2204 \
--vm-sku Standard_D4s_v5 \
--instance-count 2 \
--admin-username azureuser \
--generate-ssh-keys \
--vnet-name myapp-vnet \
--subnet app-subnet \
--upgrade-policy-mode Rolling \
--health-probe "/" \
--load-balancer myapp-lb \
--zones 1 2 3 \
--custom-data cloud-init.yaml \
--tags environment=prod
# Configure autoscale rules
az monitor autoscale create \
--resource-group compute-rg \
--resource myapp-vmss \
--resource-type Microsoft.Compute/virtualMachineScaleSets \
--name myapp-autoscale \
--min-count 2 \
--max-count 20 \
--count 3
# Scale out when CPU > 70%
az monitor autoscale rule create \
--resource-group compute-rg \
--autoscale-name myapp-autoscale \
--condition "Percentage CPU > 70 avg 5m" \
--scale out 2
# Scale in when CPU < 30%
az monitor autoscale rule create \
--resource-group compute-rg \
--autoscale-name myapp-autoscale \
--condition "Percentage CPU < 30 avg 10m" \
--scale in 1
# Manual scale
az vmss scale \
--resource-group compute-rg \
--name myapp-vmss \
--new-capacity 5
# Update VMSS image
az vmss update \
--resource-group compute-rg \
--name myapp-vmss \
--set virtualMachineProfile.storageProfile.imageReference.version=latest
# Rolling upgrade of instances
az vmss rolling-upgrade start \
--resource-group compute-rg \
--name myapp-vmss
# List VMSS instances
az vmss list-instances \
--resource-group compute-rg \
--name myapp-vmss \
--output table
```
## VM Management Operations
```bash
# Start / Stop / Restart / Deallocate
az vm start --resource-group compute-rg --name myapp-vm
az vm stop --resource-group compute-rg --name myapp-vm
az vm restart --resource-group compute-rg --name myapp-vm
az vm deallocate --resource-group compute-rg --name myapp-vm
# Run command on a VM
az vm run-command invoke \
--resource-group compute-rg \
--name myapp-vm \
--command-id RunShellScript \
--scripts "df -h && free -m && uptime"
# Enable boot diagnostics
az vm boot-diagnostics enable \
--resource-group compute-rg \
--name myapp-vm
# Get boot diagnostics log
az vm boot-diagnostics get-boot-log \
--resource-group compute-rg \
--name myapp-vm
# Enable Azure Backup
az backup protection enable-for-vm \
--resource-group compute-rg \
--vault-name myapp-vault \
--vm myapp-vm \
--policy-name DefaultPolicy
# Resize a VM
az vm resize \
--resource-group compute-rg \
--name myapp-vm \
--size Standard_D8s_v5
```
## Terraform Configuration
```hcl
resource "azurerm_linux_virtual_machine" "main" {
name = "myapp-vm"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = "Standard_D4s_v5"
admin_username = "azureuser"
zone = "1"
network_interface_ids = [azurerm_network_interface.main.id]
admin_ssh_key {
username = "azureuser"
public_key = file("~/.ssh/id_rsa.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
disk_size_gb = 64
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
identity {
type = "SystemAssigned"
}
tags = var.tags
}
resource "azurerm_managed_disk" "data" {
name = "myapp-data-disk"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
storage_account_type = "Premium_LRS"
create_option = "Empty"
disk_size_gb = 256
zone = "1"
tags = var.tags
}
resource "azurerm_virtual_machine_data_disk_attachment" "data" {
managed_disk_id = azurerm_managed_disk.data.id
virtual_machine_id = azurerm_linux_virtual_machine.main.id
lun = 0
caching = "ReadOnly"
}
resource "azurerm_linux_virtual_machine_scale_set" "main" {
name = "myapp-vmss"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
sku = "Standard_D4s_v5"
instances = 3
admin_username = "azureuser"
zones = [1, 2, 3]
admin_ssh_key {
username = "azureuser"
public_key = file("~/.ssh/id_rsa.pub")
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
network_interface {
name = "vmss-nic"
primary = true
ip_configuration {
name = "internal"
primary = true
subnet_id = azurerm_subnet.app.id
}
}
automatic_os_upgrade_policy {
disable_automatic_rollback = false
enable_automatic_os_upgrade = true
}
rolling_upgrade_policy {
max_batch_instance_percent = 20
max_unhealthy_instance_percent = 20
max_unhealthy_upgraded_instance_percent = 5
pause_time_between_batches = "PT0S"
}
tags = var.tags
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| VM fails to start | Quota exceeded in region | Check quota with `az vm list-usage --location eastus`; request increase |
| SSH connection refused | NSG blocking port 22 or VM not running | Check NSG rules and VM power state; use Azure Bastion for private VMs |
| VM disk full | OS disk too small or logs not rotated | Resize disk after deallocating; configure log rotation |
| VM performance is slow | Wrong VM size or disk throttling | Check metrics with `az monitor metrics list`; upgrade size or disk tier |
| Scale set not scaling out | Autoscale rule threshold not met | Review autoscale settings; verify metric thresholds match workload |
| Custom image VM boot fails | Image not properly generalized | Re-run `waagent -deprovision` before capturing; check boot diagnostics |
| VMSS rolling upgrade stuck | Health probe failing on new instances | Fix application health endpoint; check `az vmss rolling-upgrade get-latest` |
| Spot VM evicted unexpectedly | Azure reclaimed capacity | Use eviction policy `Deallocate` and set up eviction notifications |
## Related Skills
- `azure-networking` -- VNet and NSG configuration for VM connectivity.
- `azure-aks` -- Container alternative when VMs are not required.
- `arm-templates` -- Bicep-based VM deployment templates.
- `terraform-azure` -- Terraform-based VM and VMSS provisioning.
@@ -9,51 +9,603 @@ metadata:
# Terraform Azure
Provision Azure infrastructure with Terraform.
Provision and manage Azure infrastructure with Terraform using the AzureRM provider. Covers provider configuration, remote state, resource groups, VNets, AKS, Key Vault, complete .tf file examples, and production workflows.
## When to Use
- You need multi-cloud or cloud-agnostic Infrastructure as Code.
- Your team standardizes on Terraform across AWS, Azure, and GCP.
- You need plan/apply workflows with change preview before deployment.
- You want modular, reusable infrastructure components.
- You need state locking and drift detection for production infrastructure.
## Prerequisites
```bash
# Install Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# Verify installation
terraform version
# Install Azure CLI and login
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az login
az account set --subscription "my-subscription-id"
# Create storage account for remote state
az group create --name tfstate-rg --location eastus
az storage account create \
--name tfstate$(openssl rand -hex 4) \
--resource-group tfstate-rg \
--sku Standard_LRS \
--encryption-services blob
az storage container create \
--name tfstate \
--account-name tfstateXXXXXXXX
```
## Provider Configuration
### providers.tf
```hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
version = "~> 3.80"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 2.47"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "tfstate12345"
resource_group_name = "tfstate-rg"
storage_account_name = "tfstate12345abc"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
provider "azurerm" {
features {}
features {
key_vault {
purge_soft_delete_on_destroy = false
recover_soft_deleted_key_vaults = true
}
resource_group {
prevent_deletion_if_contains_resources = true
}
}
# Optional: use a specific subscription
# subscription_id = var.subscription_id
}
provider "azuread" {}
```
### variables.tf
```hcl
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "location" {
description = "Azure region for all resources"
type = string
default = "eastus"
}
variable "project_name" {
description = "Project name used in resource naming"
type = string
default = "myapp"
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
variable "sql_admin_password" {
description = "SQL Server admin password"
type = string
sensitive = true
}
variable "aks_admin_group_id" {
description = "Azure AD group ID for AKS admin access"
type = string
}
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = merge(var.tags, {
environment = var.environment
project = var.project_name
managed_by = "terraform"
})
}
```
## Example Resources
### terraform.tfvars (per environment)
```hcl
# terraform.prod.tfvars
environment = "prod"
location = "eastus"
project_name = "myapp"
aks_admin_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
tags = {
cost_center = "engineering"
owner = "platform-team"
}
```
## Resource Group
### resource-group.tf
```hcl
resource "azurerm_resource_group" "main" {
name = "myapp-rg"
location = "East US"
}
resource "azurerm_virtual_network" "main" {
name = "myapp-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
name = "${local.name_prefix}-rg"
location = var.location
tags = local.common_tags
}
```
## Best Practices
## Virtual Network
- Use remote state in Azure Storage
- Implement resource naming conventions
- Use data sources for existing resources
- Tag all resources
- Use modules for reusability
### network.tf
```hcl
resource "azurerm_virtual_network" "main" {
name = "${local.name_prefix}-vnet"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = ["10.0.0.0/16"]
tags = local.common_tags
}
resource "azurerm_subnet" "aks" {
name = "aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/22"]
}
resource "azurerm_subnet" "app" {
name = "app-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.8.0/24"]
}
resource "azurerm_subnet" "data" {
name = "data-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.9.0/24"]
private_endpoint_network_policies_enabled = true
}
resource "azurerm_network_security_group" "app" {
name = "${local.name_prefix}-app-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "AllowHTTPS"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = local.common_tags
}
resource "azurerm_subnet_network_security_group_association" "app" {
subnet_id = azurerm_subnet.app.id
network_security_group_id = azurerm_network_security_group.app.id
}
```
## AKS Cluster
### aks.tf
```hcl
resource "azurerm_log_analytics_workspace" "aks" {
name = "${local.name_prefix}-law"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "PerGB2018"
retention_in_days = 30
tags = local.common_tags
}
resource "azurerm_kubernetes_cluster" "main" {
name = "${local.name_prefix}-aks"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = "${var.project_name}-${var.environment}"
kubernetes_version = "1.28"
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 5
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
os_disk_size_gb = 128
os_disk_type = "Managed"
max_pods = 50
node_labels = {
role = "system"
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "calico"
service_cidr = "10.1.0.0/16"
dns_service_ip = "10.1.0.10"
load_balancer_sku = "standard"
}
azure_active_directory_role_based_access_control {
managed = true
azure_rbac_enabled = true
admin_group_object_ids = [var.aks_admin_group_id]
}
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.aks.id
}
key_vault_secrets_provider {
secret_rotation_enabled = true
secret_rotation_interval = "2m"
}
tags = local.common_tags
}
resource "azurerm_kubernetes_cluster_node_pool" "app" {
name = "app"
kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
vm_size = "Standard_D8s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 20
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
max_pods = 50
node_labels = {
workload = "app"
}
node_taints = [
"dedicated=app:NoSchedule"
]
tags = local.common_tags
}
```
## Key Vault
### keyvault.tf
```hcl
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "main" {
name = "${var.project_name}-${var.environment}-kv"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
soft_delete_retention_days = 90
purge_protection_enabled = true
enabled_for_disk_encryption = true
network_acls {
default_action = "Deny"
bypass = "AzureServices"
ip_rules = var.allowed_ip_ranges
virtual_network_subnet_ids = [
azurerm_subnet.app.id,
azurerm_subnet.aks.id,
]
}
tags = local.common_tags
}
resource "azurerm_key_vault_access_policy" "terraform" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
secret_permissions = [
"Get", "List", "Set", "Delete", "Purge", "Recover"
]
key_permissions = [
"Get", "List", "Create", "Delete", "Purge", "Recover",
"WrapKey", "UnwrapKey"
]
}
resource "azurerm_key_vault_access_policy" "aks" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_kubernetes_cluster.main.key_vault_secrets_provider[0].secret_identity[0].object_id
secret_permissions = ["Get", "List"]
}
resource "azurerm_key_vault_secret" "sql_password" {
name = "sql-admin-password"
value = var.sql_admin_password
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_key_vault_access_policy.terraform]
}
```
## SQL Database
### database.tf
```hcl
resource "azurerm_mssql_server" "main" {
name = "${local.name_prefix}-sql"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = "sqladmin"
administrator_login_password = var.sql_admin_password
minimum_tls_version = "1.2"
azuread_administrator {
login_username = "SQL Admins"
object_id = var.aks_admin_group_id
}
tags = local.common_tags
}
resource "azurerm_mssql_database" "main" {
name = "${var.project_name}-db"
server_id = azurerm_mssql_server.main.id
collation = "SQL_Latin1_General_CP1_CI_AS"
sku_name = var.environment == "prod" ? "BC_Gen5_4" : "GP_S_Gen5_2"
max_size_gb = var.environment == "prod" ? 256 : 32
zone_redundant = var.environment == "prod"
short_term_retention_policy {
retention_days = var.environment == "prod" ? 14 : 7
}
tags = local.common_tags
}
resource "azurerm_private_endpoint" "sql" {
name = "${local.name_prefix}-sql-pe"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
subnet_id = azurerm_subnet.data.id
private_service_connection {
name = "sql-connection"
private_connection_resource_id = azurerm_mssql_server.main.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
tags = local.common_tags
}
```
## Outputs
### outputs.tf
```hcl
output "resource_group_name" {
value = azurerm_resource_group.main.name
}
output "aks_cluster_name" {
value = azurerm_kubernetes_cluster.main.name
}
output "aks_kube_config" {
value = azurerm_kubernetes_cluster.main.kube_config_raw
sensitive = true
}
output "key_vault_uri" {
value = azurerm_key_vault.main.vault_uri
}
output "sql_server_fqdn" {
value = azurerm_mssql_server.main.fully_qualified_domain_name
}
output "vnet_id" {
value = azurerm_virtual_network.main.id
}
```
## Terraform Workflow Commands
```bash
# Initialize (download providers, configure backend)
terraform init
# Validate configuration syntax
terraform validate
# Format all .tf files
terraform fmt -recursive
# Plan changes for a specific environment
terraform plan \
-var-file="terraform.prod.tfvars" \
-var="sql_admin_password=$(az keyvault secret show --vault-name ops-vault --name sql-pass --query value -o tsv)" \
-out=tfplan
# Apply the saved plan
terraform apply tfplan
# Apply with auto-approve (CI/CD pipelines only)
terraform apply \
-var-file="terraform.prod.tfvars" \
-auto-approve
# Destroy infrastructure (careful!)
terraform plan -destroy -var-file="terraform.prod.tfvars" -out=destroyplan
terraform apply destroyplan
# Import existing resources into state
terraform import azurerm_resource_group.main /subscriptions/{sub}/resourceGroups/myapp-prod-rg
# Show current state
terraform state list
terraform state show azurerm_kubernetes_cluster.main
# Move resources in state (renaming)
terraform state mv azurerm_resource_group.old azurerm_resource_group.new
# Refresh state from real infrastructure
terraform refresh -var-file="terraform.prod.tfvars"
# Unlock stuck state
terraform force-unlock LOCK_ID
# Use workspaces for environment isolation
terraform workspace new prod
terraform workspace select prod
terraform workspace list
```
## Module Structure
```
project/
modules/
networking/
main.tf
variables.tf
outputs.tf
aks/
main.tf
variables.tf
outputs.tf
database/
main.tf
variables.tf
outputs.tf
environments/
dev/
main.tf
terraform.tfvars
backend.tf
prod/
main.tf
terraform.tfvars
backend.tf
```
### Using Modules
```hcl
# environments/prod/main.tf
module "networking" {
source = "../../modules/networking"
environment = var.environment
location = var.location
project_name = var.project_name
address_space = ["10.0.0.0/16"]
}
module "aks" {
source = "../../modules/aks"
environment = var.environment
location = var.location
project_name = var.project_name
resource_group_name = module.networking.resource_group_name
subnet_id = module.networking.aks_subnet_id
admin_group_id = var.aks_admin_group_id
}
module "database" {
source = "../../modules/database"
environment = var.environment
location = var.location
project_name = var.project_name
resource_group_name = module.networking.resource_group_name
subnet_id = module.networking.data_subnet_id
admin_password = var.sql_admin_password
}
```
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `Error acquiring state lock` | Previous run crashed or concurrent access | Run `terraform force-unlock LOCK_ID` after confirming no other run is active |
| `Provider version constraint error` | Version conflict in required_providers | Run `terraform init -upgrade` to fetch compatible versions |
| `Resource already exists` | Resource created outside Terraform | Import with `terraform import` to bring it under management |
| `Cycle detected` in plan | Circular dependency between resources | Restructure references or use `depends_on` carefully |
| State file corruption | Concurrent writes or manual edits | Restore from state backup in the storage account versioning |
| `AuthorizationFailed` during apply | Service principal lacks RBAC permissions | Assign Contributor role on subscription or resource group |
| Plan shows unexpected changes | Drift from manual portal changes | Run `terraform refresh` then `terraform plan` to reconcile |
| Module source not found | Incorrect relative path or registry reference | Verify path in `source` attribute; run `terraform init` again |
## Related Skills
- `arm-templates` -- Azure-native IaC alternative with Bicep.
- `azure-aks` -- AKS cluster details and kubectl operations.
- `azure-networking` -- VNet and NSG design referenced in Terraform configs.
- `azure-sql` -- Database provisioning and security configurations.
- `azure-vms` -- VM sizing and scale set configurations.