为 Bash 创建 Azure CLI 脚本
在 Azure 门户中几乎可以完成的每项任务,您也可以使用 Azure CLI 参考命令来完成。 使用 Azure 门户 了解 Azure 是一个很好的起点。 但是,我们建议使用 Azure CLI 或 Azure PowerShell 大规模管理 Azure 资源 。
请考虑为全球公司管理 Azure 的方案。 每天收到针对新 资源组、 Azure 逻辑应用、 存储帐户、 Azure 数据工厂管道和 Azure SQL 数据库的多个请求。 所有团队都在开发、测试和生产环境中工作。 对于每个请求,都需要创建三个遵循公司命名标准和安全策略的类似 Azure 资源 。 是时候使用 Azure CLI 脚本了!
了解 Azure CLI 脚本
Azure CLI 脚本可实现大规模重复任务和基础结构部署的自动化。 脚本提供一致性、减少人为错误,并启用基础结构即代码(IaC)做法。 无论是管理开发、过渡环境还是生产环境, Azure CLI 脚本都简化了资源预配和管理。
脚本结构基础知识
结构良好的 Azure CLI 脚本通常包括:
-
Shebang (
#!/bin/bash或#!/usr/bin/env bash): 指定执行脚本的翻译。 - 变量: 存储可重用的值,例如资源名称、位置和 SKU 配置。
- 评论: 记录脚本逻辑和参数以保持可维护性。
- 错误处理: 实现检查以正常处理故障。
- 循环和条件: 根据条件创建多个资源或应用逻辑。
- 输出格式: 以可读格式显示结果进行验证。
创建资源部署脚本
让我们来看看一个实际的 Bash 脚本,该脚本以一致、可重复的方式创建多个 Azure 存储帐户 。
#!/bin/bash
# Script: Create multiple Azure storage accounts
# Description: Automates storage account creation with consistent naming and configuration
# Define deployment parameters
resourceGroupName="rg-storage-prod-eastus"
saCount=3
saLocation="eastus"
saNamePrefix="stprod"
saSku="Standard_GRS"
saKind="StorageV2"
# Validate Azure CLI is installed and authenticated
if ! command -v az &> /dev/null; then
echo "Error: Azure CLI is not installed. Please install Azure CLI first."
exit 1
fi
if ! az account show &> /dev/null; then
echo "Error: Not authenticated to Azure. Please run 'az login' first."
exit 1
fi
echo "Starting storage account deployment..."
echo "Resource Group: $resourceGroupName"
echo "Location: $saLocation"
echo "Count: $saCount"
echo ""
# Create resource group if it doesn't exist
if ! az group show --name $resourceGroupName &> /dev/null; then
echo "Creating resource group $resourceGroupName..."
az group create --name $resourceGroupName --location $saLocation
else
echo "Resource group $resourceGroupName already exists."
fi
# Loop to create multiple storage accounts
for i in $(seq 1 $saCount)
do
# Generate unique identifier using timestamp and random number
timestamp=$(date +%s)
let "randomIdentifier=$RANDOM"
saName="${saNamePrefix}${timestamp}${randomIdentifier}"
# Ensure name is lowercase and within 24 character limit
saName=$(echo $saName | tr '[:upper:]' '[:lower:]' | cut -c1-24)
echo "Creating storage account $saName..."
# Create storage account with error handling
if az storage account create \
--name $saName \
--resource-group $resourceGroupName \
--location $saLocation \
--sku $saSku \
--kind $saKind \
--tags Environment=Production ManagedBy=AzureCLI \
--output none; then
echo "✓ Successfully created storage account $saName"
else
echo "✗ Failed to create storage account $saName"
fi
done
echo ""
echo "Deployment complete. Verifying results..."
echo ""
# Verify results with formatted output
az storage account list \
--resource-group $resourceGroupName \
--query "[].{Name:name, Location:location, SKU:sku.name, Status:statusOfPrimary}" \
--output table
脚本分解
让我们检查关键组件:
身份验证验证:
if ! az account show &> /dev/null; then
echo "Error: Not authenticated to Azure. Please run 'az login' first."
exit 1
fi
确保在执行资源作之前对用户进行身份验证,防止脚本失败。
使用存在检查创建资源组:
if ! az group show --name $resourceGroupName &> /dev/null; then
az group create --name $resourceGroupName --location $saLocation
fi
仅当资源组尚不存在时,才创建资源组,使脚本具有幂等性。
独特名称生成:
timestamp=$(date +%s)
let "randomIdentifier=$RANDOM"
saName="${saNamePrefix}${timestamp}${randomIdentifier}"
saName=$(echo $saName | tr '[:upper:]' '[:lower:]' | cut -c1-24)
组合时间戳和随机数以生成唯一的存储帐户名称,转换为小写,并按 Azure 命名要求限制为 24 个字符。
错误处理:
if az storage account create ...; then
echo "✓ Successfully created storage account $saName"
else
echo "✗ Failed to create storage account $saName"
fi
验证每个存储帐户的创建,并提供明确的成功/失败反馈。
创建参数化脚本
生产脚本应接受用于跨环境灵活性的参数:
#!/bin/bash
# Script: create-storage-accounts.sh
# Usage: ./create-storage-accounts.sh <resource-group> <location> <count> <environment>
# Accept command-line parameters
resourceGroupName=$1
saLocation=$2
saCount=$3
environment=$4
# Validate parameters
if [ -z "$resourceGroupName" ] || [ -z "$saLocation" ] || [ -z "$saCount" ] || [ -z "$environment" ]; then
echo "Usage: $0 <resource-group> <location> <count> <environment>"
echo "Example: $0 rg-storage-dev eastus 3 Development"
exit 1
fi
# Validate count is a number
if ! [[ "$saCount" =~ ^[0-9]+$ ]]; then
echo "Error: Count must be a number"
exit 1
fi
echo "Parameters received:"
echo " Resource Group: $resourceGroupName"
echo " Location: $saLocation"
echo " Count: $saCount"
echo " Environment: $environment"
echo ""
# Set environment-specific configurations
case $environment in
Development)
saSku="Standard_LRS"
saKind="StorageV2"
;;
Staging)
saSku="Standard_GRS"
saKind="StorageV2"
;;
Production)
saSku="Standard_RAGRS"
saKind="StorageV2"
;;
*)
echo "Error: Environment must be Development, Staging, or Production"
exit 1
;;
esac
echo "Using SKU: $saSku for $environment environment"
echo ""
# Continue with resource creation...
执行参数化脚本
# Make script executable
chmod +x create-storage-accounts.sh
# Execute with parameters
./create-storage-accounts.sh rg-storage-dev eastus 3 Development
创建配置驱动脚本
对于复杂的部署,请使用外部配置文件:
config.json:
{
"resourceGroup": {
"name": "rg-app-prod-eastus",
"location": "eastus"
},
"storageAccounts": [
{
"namePrefix": "stappdata",
"sku": "Standard_RAGRS",
"kind": "StorageV2",
"count": 2
},
{
"namePrefix": "stappbackup",
"sku": "Standard_GRS",
"kind": "StorageV2",
"count": 1
}
],
"tags": {
"Environment": "Production",
"CostCenter": "Engineering",
"ManagedBy": "AzureCLI"
}
}
deploy-from-config.sh:
#!/bin/bash
# Load configuration file
configFile="config.json"
if [ ! -f "$configFile" ]; then
echo "Error: Configuration file $configFile not found"
exit 1
fi
# Parse configuration using jq (JSON processor)
resourceGroupName=$(jq -r '.resourceGroup.name' $configFile)
resourceGroupLocation=$(jq -r '.resourceGroup.location' $configFile)
tagsJson=$(jq -r '.tags | to_entries | map("\(.key)=\(.value)") | join(" ")' $configFile)
echo "Deploying resources from configuration file..."
echo "Resource Group: $resourceGroupName"
echo "Location: $resourceGroupLocation"
echo ""
# Create resource group
az group create --name $resourceGroupName --location $resourceGroupLocation
# Read storage account configurations
storageConfigCount=$(jq '.storageAccounts | length' $configFile)
for i in $(seq 0 $(($storageConfigCount - 1)))
do
namePrefix=$(jq -r ".storageAccounts[$i].namePrefix" $configFile)
sku=$(jq -r ".storageAccounts[$i].sku" $configFile)
kind=$(jq -r ".storageAccounts[$i].kind" $configFile)
count=$(jq -r ".storageAccounts[$i].count" $configFile)
echo "Creating $count storage account(s) with prefix: $namePrefix"
for j in $(seq 1 $count)
do
timestamp=$(date +%s)
saName="${namePrefix}${timestamp}${RANDOM}"
saName=$(echo $saName | tr '[:upper:]' '[:lower:]' | cut -c1-24)
az storage account create \
--name $saName \
--resource-group $resourceGroupName \
--location $resourceGroupLocation \
--sku $sku \
--kind $kind \
--tags $tagsJson \
--output none
echo " ✓ Created: $saName"
done
done
echo ""
echo "Deployment complete."
配置驱动的优势
- 关注点分离: 使配置与部署逻辑分离。
- 环境提升:针对开发、暂存、生产环境使用相同脚本但配置文件不同。
- 版本控制: 跟踪配置更改以及代码。
- 验证: 在部署之前验证配置结构。
使用脚本删除 Azure 资源
创建和测试脚本时,请记住删除测试资源以避免不必要的成本。 Azure CLI 脚本支持安全、一致的资源清理。
按创建日期删除存储帐户
删除在特定日期和时间或之后创建的所有 存储帐户 :
#!/bin/bash
# Define cleanup parameters
cutoffDate="2025-10-08T00:00:00.000000+00:00"
resourceGroup="rg-storage-dev-eastus"
echo "Finding storage accounts created on or after $cutoffDate..."
# Get list of storage accounts matching criteria
saList=$(az storage account list \
--resource-group $resourceGroup \
--query "[?creationTime >='$cutoffDate'].{Name:name, Created:creationTime}" \
--output table)
echo "$saList"
echo ""
# Confirm deletion
read -p "Delete these storage accounts? (yes/no): " confirm
if [ "$confirm" == "yes" ]; then
for saId in $(az storage account list \
--resource-group $resourceGroup \
--query "[?creationTime >='$cutoffDate'].id" \
--output tsv); do
echo "Deleting storage account: $saId"
az storage account delete --ids $saId --yes
done
echo "Cleanup complete."
else
echo "Deletion cancelled."
fi
使用记录删除资源组
删除与具有全面日志记录的命名模式匹配的 资源组 :
#!/bin/bash
# Define cleanup parameters
namePattern="rg-dev-*"
logFileLocation="cleanup-$(date +%Y%m%d-%H%M%S).log"
echo "Resource Group Cleanup" > $logFileLocation
echo "Started: $(date)" >> $logFileLocation
echo "Pattern: $namePattern" >> $logFileLocation
echo "----------------------------------------" >> $logFileLocation
# Find matching resource groups
echo "Finding resource groups matching pattern: $namePattern"
matchingGroups=$(az group list \
--query "[?starts_with(name, 'rg-dev-')].name" \
--output tsv)
if [ -z "$matchingGroups" ]; then
echo "No resource groups found matching pattern: $namePattern"
echo "No resource groups found" >> $logFileLocation
exit 0
fi
# Display matches
echo "Found resource groups:"
echo "$matchingGroups"
echo ""
# Log matches
echo "Resource groups found:" >> $logFileLocation
echo "$matchingGroups" >> $logFileLocation
echo "" >> $logFileLocation
# Confirm deletion
read -p "Delete these resource groups? (yes/no): " confirm
if [ "$confirm" == "yes" ]; then
echo "Starting deletion..." >> $logFileLocation
for rgName in $matchingGroups
do
echo "Deleting resource group: $rgName"
echo "$(date): Deleting $rgName" >> $logFileLocation
# Delete with --no-wait for background execution
if az group delete --name $rgName --yes --no-wait; then
echo " ✓ Deletion initiated for $rgName"
echo "$(date): ✓ Deletion initiated for $rgName" >> $logFileLocation
else
echo " ✗ Failed to initiate deletion for $rgName"
echo "$(date): ✗ Failed to delete $rgName" >> $logFileLocation
fi
done
echo ""
echo "Deletion operations initiated. Resources will be removed in the background."
echo "Check deletion status with: az group list --query \"[?starts_with(name, 'rg-dev-')].name\""
echo "" >> $logFileLocation
echo "Completed: $(date)" >> $logFileLocation
echo "Log saved to: $logFileLocation"
# Display log
echo ""
echo "=== Cleanup Log ==="
cat $logFileLocation
else
echo "Deletion cancelled by user" >> $logFileLocation
echo "Deletion cancelled."
fi
安全删除做法
在删除之前实施安全检查:
#!/bin/bash
# Delete resources safely with multiple confirmations
resourceGroup="rg-storage-test-eastus"
# Check if resource group exists
if ! az group show --name $resourceGroup &> /dev/null; then
echo "Error: Resource group '$resourceGroup' not found"
exit 1
fi
# Display resources that will be deleted
echo "Resources in resource group '$resourceGroup':"
az resource list --resource-group $resourceGroup \
--query "[].{Name:name, Type:type, Location:location}" \
--output table
echo ""
echo "⚠️ WARNING: This will delete ALL resources in '$resourceGroup'"
echo ""
# First confirmation
read -p "Are you sure you want to delete '$resourceGroup'? (yes/no): " confirm1
if [ "$confirm1" != "yes" ]; then
echo "Deletion cancelled."
exit 0
fi
# Second confirmation with exact name
read -p "Type the resource group name to confirm: " confirm2
if [ "$confirm2" != "$resourceGroup" ]; then
echo "Resource group name does not match. Deletion cancelled."
exit 1
fi
# Final countdown
echo "Deleting in 5 seconds. Press Ctrl+C to cancel."
sleep 5
echo "Deleting resource group '$resourceGroup'..."
az group delete --name $resourceGroup --yes
echo "Resource group deleted successfully."
警告
在删除之前,请始终验证资源。 删除 资源组 会永久删除其中的所有资源。 此操作不可撤消。
生产脚本的最佳做法
生产就绪 的 Azure CLI 脚本需要仔细规划和实施。 本单元中提供的脚本演示了基本概念,但生产环境需要其他注意事项。
基本生产脚本组件
适用于生产环境的可靠 Azure CLI 脚本应包括:
1.身份验证和授权:
#!/bin/bash
# Authenticate with service principal (for automation)
if [ -n "$AZURE_CLIENT_ID" ] && [ -n "$AZURE_CLIENT_SECRET" ] && [ -n "$AZURE_TENANT_ID" ]; then
echo "Authenticating with service principal..."
az login --service-principal \
--username $AZURE_CLIENT_ID \
--password $AZURE_CLIENT_SECRET \
--tenant $AZURE_TENANT_ID
else
echo "Error: Service principal credentials not found"
exit 1
fi
# Set subscription
az account set --subscription $AZURE_SUBSCRIPTION_ID
# Verify authentication
if ! az account show &> /dev/null; then
echo "Error: Authentication failed"
exit 1
fi
2. 全面的错误处理:
#!/bin/bash
# Exit immediately if command fails
set -e
# Trap errors and perform cleanup
trap 'cleanup_on_error' ERR
cleanup_on_error() {
echo "Error occurred on line $1"
echo "Performing cleanup..."
# Add cleanup logic here
exit 1
}
# Enable error trapping with line numbers
trap 'cleanup_on_error $LINENO' ERR
3. 幂等性检查:
#!/bin/bash
# Check if resource group exists before creation
create_resource_group() {
local rgName=$1
local location=$2
if az group show --name $rgName &> /dev/null; then
echo "Resource group $rgName already exists. Skipping creation."
else
echo "Creating resource group $rgName..."
az group create --name $rgName --location $location
fi
}
# Check if storage account exists
create_storage_account() {
local saName=$1
local rgName=$2
if az storage account show --name $saName --resource-group $rgName &> /dev/null; then
echo "Storage account $saName already exists. Skipping creation."
return 0
else
echo "Creating storage account $saName..."
az storage account create --name $saName --resource-group $rgName --location eastus
fi
}
4. 结构化日志记录:
#!/bin/bash
# Define log file with timestamp
logFile="deployment-$(date +%Y%m%d-%H%M%S).log"
errorLog="errors-$(date +%Y%m%d-%H%M%S).log"
# Logging function
log() {
local level=$1
shift
local message="$@"
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] [$level] $message" | tee -a $logFile
if [ "$level" == "ERROR" ]; then
echo "[$timestamp] $message" >> $errorLog
fi
}
# Usage
log "INFO" "Starting deployment..."
log "ERROR" "Failed to create resource"
log "SUCCESS" "Deployment complete"
5.配置验证:
#!/bin/bash
# Validate required configuration
validate_config() {
local errors=0
if [ -z "$RESOURCE_GROUP" ]; then
echo "Error: RESOURCE_GROUP not defined"
((errors++))
fi
if [ -z "$LOCATION" ]; then
echo "Error: LOCATION not defined"
((errors++))
fi
# Validate location exists
if ! az account list-locations --query "[?name=='$LOCATION']" | grep -q "$LOCATION"; then
echo "Error: Invalid location: $LOCATION"
((errors++))
fi
if [ $errors -gt 0 ]; then
echo "Configuration validation failed with $errors error(s)"
exit 1
fi
echo "Configuration validation passed"
}
6. 标记策略:
#!/bin/bash
# Define standard tags
environment=$1 # Development, Staging, Production
costCenter=$2
owner=$3
# Create tags string
tags="Environment=$environment CostCenter=$costCenter Owner=$owner \
CreatedBy=AzureCLI CreatedDate=$(date +%Y-%m-%d) \
ManagedBy=Infrastructure-Team Project=WebApp"
# Apply tags to resources
az group create \
--name $resourceGroup \
--location $location \
--tags $tags
az storage account create \
--name $saName \
--resource-group $resourceGroup \
--location $location \
--tags $tags
7. 回滚功能:
#!/bin/bash
# Track created resources for rollback
createdResources=()
create_with_tracking() {
local resourceType=$1
local resourceName=$2
# Attempt resource creation
if create_resource "$resourceType" "$resourceName"; then
createdResources+=("$resourceType:$resourceName")
echo "✓ Created: $resourceType - $resourceName"
return 0
else
echo "✗ Failed: $resourceType - $resourceName"
return 1
fi
}
rollback() {
echo "Rolling back created resources..."
for resource in "${createdResources[@]}"; do
IFS=':' read -r type name <<< "$resource"
echo "Deleting $type: $name"
delete_resource "$type" "$name"
done
echo "Rollback complete"
}
# Use trap to rollback on error
trap rollback ERR
脚本组织结构
将脚本组织到逻辑模块中:
azure-infrastructure/
├── config/
│ ├── dev.json
│ ├── staging.json
│ └── prod.json
├── scripts/
│ ├── deploy-infrastructure.sh
│ ├── deploy-storage.sh
│ └── cleanup.sh
├── lib/
│ ├── common-functions.sh
│ ├── logging.sh
│ └── validation.sh
├── logs/
└── README.md
可重用函数库
common-functions.sh:
#!/bin/bash
# Load this file in other scripts: source ./lib/common-functions.sh
# Check if Azure CLI is installed
check_azure_cli() {
if ! command -v az &> /dev/null; then
echo "Error: Azure CLI not installed"
exit 1
fi
}
# Wait for resource to be ready
wait_for_resource() {
local resourceId=$1
local maxAttempts=30
local attempt=1
echo "Waiting for resource to be ready..."
while [ $attempt -le $maxAttempts ]; do
if az resource show --ids $resourceId &> /dev/null; then
echo "Resource is ready"
return 0
fi
echo "Attempt $attempt/$maxAttempts - waiting..."
sleep 10
((attempt++))
done
echo "Timeout waiting for resource"
return 1
}
# Generate unique name
generate_unique_name() {
local prefix=$1
local timestamp=$(date +%s)
local random=$RANDOM
echo "${prefix}${timestamp}${random}" | tr '[:upper:]' '[:lower:]' | cut -c1-24
}
安全地测试脚本
在隔离环境中测试脚本:
#!/bin/bash
# Test mode flag
TEST_MODE=${TEST_MODE:-false}
execute_command() {
local command=$1
if [ "$TEST_MODE" == "true" ]; then
echo "[TEST MODE] Would execute: $command"
else
echo "Executing: $command"
eval $command
fi
}
# Usage
execute_command "az group create --name rg-test --location eastus"
在测试模式下运行:
TEST_MODE=true ./deploy-infrastructure.sh
其他资源
- Azure CLI 示例存储库:github.com/Azure-Samples/azure-cli-samples。
- Azure CLI 脚本指南:learn.microsoft.com/cli/azure/azure-cli-vm-tutorial。
- Bash 脚本最佳做法:www.gnu.org/software/bash/manual/。
本单元中的脚本演示了将变量、循环、条件和错误处理与 Azure CLI 命令结合使用时的 Azure CLI 功能。 这些模式从管理少量资源扩展到协调复杂的企业基础设施部署。