在现代IT运维领域,基础设施即代码(Infrastructure as Code, IaC)已成为标准实践。本文将介绍如何使用PowerShell实现高效的IaC解决方案。
首先,让我们创建一个基础环境配置定义函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| function New-InfrastructureDefinition { param( [Parameter(Mandatory)] [string]$EnvironmentName, [Parameter(Mandatory)] [string]$OutputPath, [string]$Description = "", [ValidateSet("Development", "Testing", "Staging", "Production")] [string]$EnvironmentType = "Development", [switch]$Force ) try { $infrastructureDefinition = [PSCustomObject]@{ EnvironmentName = $EnvironmentName EnvironmentType = $EnvironmentType Description = $Description CreatedBy = $env:USERNAME CreatedOn = Get-Date Version = "1.0" Resources = @{ VirtualMachines = @() NetworkResources = @() StorageResources = @() SecurityResources = @() DatabaseResources = @() ApplicationResources = @() } Dependencies = @{} DeploymentOrder = @() State = "Draft" Metadata = @{} } $definitionFile = Join-Path -Path $OutputPath -ChildPath "$EnvironmentName.json" if ((Test-Path -Path $definitionFile) -and (-not $Force)) { throw "定义文件 '$definitionFile' 已存在。使用 -Force 参数覆盖现有文件。" } if (-not (Test-Path -Path $OutputPath)) { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null } $infrastructureDefinition | ConvertTo-Json -Depth 10 | Out-File -FilePath $definitionFile -Encoding UTF8 Write-Host "已创建基础设施定义: $definitionFile" -ForegroundColor Green return $infrastructureDefinition } catch { Write-Error "创建基础设施定义时出错: $_" } }
|
接下来,添加虚拟机资源定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| function Add-VMResource { param( [Parameter(Mandatory, ValueFromPipeline)] [PSObject]$InfrastructureDefinition, [Parameter(Mandatory)] [string]$VMName, [Parameter(Mandatory)] [string]$Size, [string]$OSType = "Windows", [string]$OSVersion = "2022-Datacenter", [string]$NetworkName, [string]$SubnetName, [hashtable]$Tags = @{}, [string[]]$DependsOn = @(), [hashtable]$Properties = @{}, [string]$OutputPath ) process { try { $vmResource = [PSCustomObject]@{ Name = $VMName ResourceType = "VirtualMachine" Size = $Size OSType = $OSType OSVersion = $OSVersion NetworkName = $NetworkName SubnetName = $SubnetName Tags = $Tags Properties = $Properties DependsOn = $DependsOn ResourceId = [guid]::NewGuid().ToString() CreatedOn = Get-Date } $InfrastructureDefinition.Resources.VirtualMachines += $vmResource foreach ($dependency in $DependsOn) { if (-not $InfrastructureDefinition.Dependencies.ContainsKey($VMName)) { $InfrastructureDefinition.Dependencies[$VMName] = @() } $InfrastructureDefinition.Dependencies[$VMName] += $dependency } if ($OutputPath) { $definitionFile = Join-Path -Path $OutputPath -ChildPath "$($InfrastructureDefinition.EnvironmentName).json" $InfrastructureDefinition | ConvertTo-Json -Depth 10 | Out-File -FilePath $definitionFile -Encoding UTF8 Write-Host "已更新基础设施定义: $definitionFile" -ForegroundColor Green } return $InfrastructureDefinition } catch { Write-Error "添加虚拟机资源定义时出错: $_" } } }
|
添加网络资源定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| function Add-NetworkResource { param( [Parameter(Mandatory, ValueFromPipeline)] [PSObject]$InfrastructureDefinition, [Parameter(Mandatory)] [string]$NetworkName, [Parameter(Mandatory)] [string]$AddressSpace, [Parameter(Mandatory)] [PSObject[]]$Subnets, [bool]$EnableDnsSupport = $true, [hashtable]$Tags = @{}, [string[]]$DependsOn = @(), [hashtable]$Properties = @{}, [string]$OutputPath ) process { try { $networkResource = [PSCustomObject]@{ Name = $NetworkName ResourceType = "VirtualNetwork" AddressSpace = $AddressSpace Subnets = $Subnets EnableDnsSupport = $EnableDnsSupport Tags = $Tags Properties = $Properties DependsOn = $DependsOn ResourceId = [guid]::NewGuid().ToString() CreatedOn = Get-Date } $InfrastructureDefinition.Resources.NetworkResources += $networkResource foreach ($dependency in $DependsOn) { if (-not $InfrastructureDefinition.Dependencies.ContainsKey($NetworkName)) { $InfrastructureDefinition.Dependencies[$NetworkName] = @() } $InfrastructureDefinition.Dependencies[$NetworkName] += $dependency } if ($OutputPath) { $definitionFile = Join-Path -Path $OutputPath -ChildPath "$($InfrastructureDefinition.EnvironmentName).json" $InfrastructureDefinition | ConvertTo-Json -Depth 10 | Out-File -FilePath $definitionFile -Encoding UTF8 Write-Host "已更新基础设施定义: $definitionFile" -ForegroundColor Green } return $InfrastructureDefinition } catch { Write-Error "添加网络资源定义时出错: $_" } } }
|
生成部署顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| function Set-DeploymentOrder { param( [Parameter(Mandatory, ValueFromPipeline)] [PSObject]$InfrastructureDefinition, [switch]$Validate, [string]$OutputPath ) process { try { $dependencyGraph = @{} $resourceList = @() foreach ($vmResource in $InfrastructureDefinition.Resources.VirtualMachines) { $resourceList += $vmResource.Name $dependencyGraph[$vmResource.Name] = $vmResource.DependsOn } foreach ($netResource in $InfrastructureDefinition.Resources.NetworkResources) { $resourceList += $netResource.Name $dependencyGraph[$netResource.Name] = $netResource.DependsOn } $visited = @{} $recStack = @{} function Test-CyclicDependency { param([string]$Node) $visited[$Node] = $true $recStack[$Node] = $true foreach ($neighbor in $dependencyGraph[$Node]) { if (-not $visited.ContainsKey($neighbor)) { if (Test-CyclicDependency -Node $neighbor) { return $true } } elseif ($recStack.ContainsKey($neighbor)) { return $true } } $recStack.Remove($Node) return $false } foreach ($resource in $resourceList) { if (-not $visited.ContainsKey($resource)) { if (Test-CyclicDependency -Node $resource) { throw "检测到循环依赖关系。无法确定部署顺序。" } } } $visited = @{} $deploymentOrder = [System.Collections.ArrayList]::new() function Sort-Topology { param([string]$Node) $visited[$Node] = $true foreach ($neighbor in $dependencyGraph[$Node]) { if (-not $visited.ContainsKey($neighbor)) { Sort-Topology -Node $neighbor } } $deploymentOrder.Add($Node) | Out-Null } foreach ($resource in $resourceList) { if (-not $visited.ContainsKey($resource)) { Sort-Topology -Node $resource } } [Array]::Reverse($deploymentOrder) $InfrastructureDefinition.DeploymentOrder = $deploymentOrder if ($Validate) { Write-Host "验证部署顺序..." -ForegroundColor Yellow $deploymentSet = @{} $isValid = $true foreach ($resource in $deploymentOrder) { foreach ($dependency in $dependencyGraph[$resource]) { if (-not $deploymentSet.ContainsKey($dependency)) { Write-Host "错误: 资源 '$resource' 依赖于 '$dependency',但该资源尚未部署。" -ForegroundColor Red $isValid = $false } } $deploymentSet[$resource] = $true } if ($isValid) { Write-Host "部署顺序有效。" -ForegroundColor Green } else { throw "部署顺序无效。请检查资源依赖关系。" } } if ($OutputPath) { $definitionFile = Join-Path -Path $OutputPath -ChildPath "$($InfrastructureDefinition.EnvironmentName).json" $InfrastructureDefinition | ConvertTo-Json -Depth 10 | Out-File -FilePath $definitionFile -Encoding UTF8 Write-Host "已更新基础设施定义: $definitionFile" -ForegroundColor Green } return $InfrastructureDefinition } catch { Write-Error "生成部署顺序时出错: $_" } } }
|
部署基础设施资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| function Deploy-InfrastructureResource { param( [Parameter(Mandatory)] [PSObject]$Resource, [Parameter(Mandatory)] [PSObject]$InfrastructureDefinition, [switch]$WhatIf, [PSObject]$DeploymentContext = @{} ) try { $resourceName = $Resource.Name $resourceType = $Resource.ResourceType Write-Host "开始部署资源: $resourceName (类型: $resourceType)" -ForegroundColor Cyan switch ($resourceType) { "VirtualMachine" { if ($WhatIf) { Write-Host "[WhatIf] 将创建虚拟机 '$resourceName' (大小: $($Resource.Size), OS: $($Resource.OSType)-$($Resource.OSVersion))" -ForegroundColor Yellow } else { Write-Host "正在创建虚拟机 '$resourceName'..." -ForegroundColor White Start-Sleep -Seconds 2 $deploymentResult = [PSCustomObject]@{ ResourceName = $resourceName ResourceType = $resourceType Status = "Success" DeploymentId = [guid]::NewGuid().ToString() DeploymentTime = Get-Date Properties = @{ IPAddress = "10.0.0.$((Get-Random -Minimum 2 -Maximum 255))" FQDN = "$resourceName.example.com" } } Write-Host "虚拟机 '$resourceName' 部署成功" -ForegroundColor Green return $deploymentResult } } "VirtualNetwork" { if ($WhatIf) { Write-Host "[WhatIf] 将创建虚拟网络 '$resourceName' (地址空间: $($Resource.AddressSpace))" -ForegroundColor Yellow } else { Write-Host "正在创建虚拟网络 '$resourceName'..." -ForegroundColor White Start-Sleep -Seconds 1 $deploymentResult = [PSCustomObject]@{ ResourceName = $resourceName ResourceType = $resourceType Status = "Success" DeploymentId = [guid]::NewGuid().ToString() DeploymentTime = Get-Date Properties = @{ AddressSpace = $Resource.AddressSpace SubnetCount = $Resource.Subnets.Count } } Write-Host "虚拟网络 '$resourceName' 部署成功" -ForegroundColor Green return $deploymentResult } } default { Write-Warning "不支持的资源类型: $resourceType" } } if ($WhatIf) { return [PSCustomObject]@{ ResourceName = $resourceName ResourceType = $resourceType Status = "WhatIf" } } } catch { Write-Error "部署资源 '$resourceName' 时出错: $_" return [PSCustomObject]@{ ResourceName = $resourceName ResourceType = $resourceType Status = "Failed" Error = $_.ToString() } } }
|
执行完整基础设施部署:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| function Deploy-Infrastructure { param( [Parameter(Mandatory)] [PSObject]$InfrastructureDefinition, [switch]$WhatIf, [switch]$Force, [string]$DeploymentLogPath, [scriptblock]$OnSuccessAction, [scriptblock]$OnFailureAction ) try { $environmentName = $InfrastructureDefinition.EnvironmentName $deploymentResults = @() $deploymentContext = @{} $deploymentStart = Get-Date $deploymentSuccess = $true Write-Host "开始部署环境: $environmentName" -ForegroundColor Cyan if ($InfrastructureDefinition.DeploymentOrder.Count -eq 0) { Write-Warning "部署顺序为空。正在尝试生成部署顺序..." $InfrastructureDefinition = Set-DeploymentOrder -InfrastructureDefinition $InfrastructureDefinition -Validate } $resourceMap = @{} foreach ($vmResource in $InfrastructureDefinition.Resources.VirtualMachines) { $resourceMap[$vmResource.Name] = $vmResource } foreach ($netResource in $InfrastructureDefinition.Resources.NetworkResources) { $resourceMap[$netResource.Name] = $netResource } foreach ($resourceName in $InfrastructureDefinition.DeploymentOrder) { $resource = $resourceMap[$resourceName] if (-not $resource) { Write-Warning "资源 '$resourceName' 在部署顺序中但未找到资源定义。跳过。" continue } $result = Deploy-InfrastructureResource -Resource $resource -InfrastructureDefinition $InfrastructureDefinition -WhatIf:$WhatIf -DeploymentContext $deploymentContext $deploymentResults += $result if ($result.Status -eq "Failed" -and -not $Force) { Write-Error "资源 '$resourceName' 部署失败。终止部署。" $deploymentSuccess = $false break } if ($result.Status -eq "Success") { $deploymentContext[$resourceName] = $result } } $deploymentEnd = Get-Date $deploymentDuration = $deploymentEnd - $deploymentStart $deploymentSummary = [PSCustomObject]@{ EnvironmentName = $environmentName StartTime = $deploymentStart EndTime = $deploymentEnd Duration = $deploymentDuration Status = if ($deploymentSuccess) { "Success" } else { "Failed" } ResourceCount = $deploymentResults.Count SuccessCount = ($deploymentResults | Where-Object { $_.Status -eq "Success" }).Count FailedCount = ($deploymentResults | Where-Object { $_.Status -eq "Failed" }).Count WhatIfCount = ($deploymentResults | Where-Object { $_.Status -eq "WhatIf" }).Count DetailedResults = $deploymentResults } if ($DeploymentLogPath) { $logFile = Join-Path -Path $DeploymentLogPath -ChildPath "Deployment_$environmentName`_$(Get-Date -Format 'yyyyMMdd_HHmmss').json" if (-not (Test-Path -Path $DeploymentLogPath)) { New-Item -Path $DeploymentLogPath -ItemType Directory -Force | Out-Null } $deploymentSummary | ConvertTo-Json -Depth 10 | Out-File -FilePath $logFile -Encoding UTF8 Write-Host "部署日志已保存至: $logFile" -ForegroundColor Green } if ($deploymentSuccess -and $OnSuccessAction) { & $OnSuccessAction -DeploymentSummary $deploymentSummary } elseif (-not $deploymentSuccess -and $OnFailureAction) { & $OnFailureAction -DeploymentSummary $deploymentSummary } Write-Host "部署摘要:" -ForegroundColor Cyan Write-Host " 环境: $environmentName" -ForegroundColor White Write-Host " 状态: $($deploymentSummary.Status)" -ForegroundColor $(if ($deploymentSummary.Status -eq "Success") { "Green" } else { "Red" }) Write-Host " 持续时间: $($deploymentDuration.TotalMinutes) 分钟" -ForegroundColor White Write-Host " 资源总数: $($deploymentSummary.ResourceCount)" -ForegroundColor White Write-Host " 成功: $($deploymentSummary.SuccessCount)" -ForegroundColor Green Write-Host " 失败: $($deploymentSummary.FailedCount)" -ForegroundColor $(if ($deploymentSummary.FailedCount -gt 0) { "Red" } else { "White" }) Write-Host " WhatIf: $($deploymentSummary.WhatIfCount)" -ForegroundColor Yellow return $deploymentSummary } catch { Write-Error "部署环境 '$environmentName' 时出错: $_" } }
|
现在,让我们看一个使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| $outputPath = "C:\IaC\Definitions"
$envDef = New-InfrastructureDefinition -EnvironmentName "DevTest" -EnvironmentType "Development" -OutputPath $outputPath -Description "开发测试环境"
$subnets = @( [PSCustomObject]@{ Name = "Frontend"; AddressPrefix = "10.0.1.0/24" }, [PSCustomObject]@{ Name = "Backend"; AddressPrefix = "10.0.2.0/24" }, [PSCustomObject]@{ Name = "Database"; AddressPrefix = "10.0.3.0/24" } )
$envDef = Add-NetworkResource -InfrastructureDefinition $envDef -NetworkName "DevVNet" -AddressSpace "10.0.0.0/16" -Subnets $subnets -OutputPath $outputPath
$envDef = Add-VMResource -InfrastructureDefinition $envDef -VMName "WebServer01" -Size "Standard_B2s" -OSType "Windows" -OSVersion "2022-Datacenter" -NetworkName "DevVNet" -SubnetName "Frontend" -DependsOn @("DevVNet") -OutputPath $outputPath
$envDef = Add-VMResource -InfrastructureDefinition $envDef -VMName "AppServer01" -Size "Standard_B2s" -OSType "Windows" -OSVersion "2022-Datacenter" -NetworkName "DevVNet" -SubnetName "Backend" -DependsOn @("DevVNet", "WebServer01") -OutputPath $outputPath
$envDef = Add-VMResource -InfrastructureDefinition $envDef -VMName "DbServer01" -Size "Standard_B2ms" -OSType "Windows" -OSVersion "2022-Datacenter" -NetworkName "DevVNet" -SubnetName "Database" -DependsOn @("DevVNet") -OutputPath $outputPath
$envDef = Set-DeploymentOrder -InfrastructureDefinition $envDef -Validate -OutputPath $outputPath
$deploymentPreview = Deploy-Infrastructure -InfrastructureDefinition $envDef -WhatIf -DeploymentLogPath "C:\IaC\Logs"
$deploymentResult = Deploy-Infrastructure -InfrastructureDefinition $envDef -DeploymentLogPath "C:\IaC\Logs"
|
这些PowerShell函数提供了一个强大的基础设施即代码解决方案,可以帮助自动化环境部署流程。随着基础设施复杂性的增加,您可以扩展这些函数来支持更多的资源类型和部署场景。结合版本控制系统如Git,这种方法能够确保环境配置的一致性和可重复性,从而提高IT运维的效率和可靠性。