iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
IT Operation

系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server系列 第 28 篇

Day 28|PowerShell Release 與部署:測試通過後,怎麼安全更新 Automation Server?

  • 分享至 

  • xImage
  •  

Day 27 我們已經把整個流程做到:
PowerShell Code
↓
Git Commit
↓
Git Push
↓
CI Pipeline
↓
Test-ModuleManifest
↓
Pester
↓
Quality Gate
↓
PASS

到這一步,我們已經可以回答:
這一版 Code 通過目前定義的自動化測試了。

但是還有最後一個非常現實的問題:
測試通過的 Code,要怎麼放到真正執行每天排程的 Automation Server?

如果現在還是:
工程師打開檔案總管
↓
Copy SysAdminToolkit
↓
貼到 Production Server
↓
選擇「取代目的地檔案」
↓
祈禱沒有漏東西

那我們前面建立:
Module
Testing
Git
CI

的價值,在 Deployment 這一段可能又全部掉回人工操作。
更危險的是,假設 Automation Server 每天 06:00 正在跑:
Server Health Check

05:59 我們直接覆蓋:
SysAdminToolkit.psm1

更新到一半,Task Scheduler 剛好啟動。
很可能出現:
新 psm1
+
舊 Public Function
+
漏掉一個檔案

這不是我希望的 Release 方式。
所以 Day 28 要正式處理:
Release & Deployment
今天的目標會變成:
Git Commit
↓
CI PASS
↓
Release Version
↓
Git Tag
↓
Build Package
↓
Hash / Validation
↓
Approval
↓
Deploy Side-by-Side
↓
Smoke Test
↓
Switch Version
↓
Production

而不是:
CI PASS
↓
直接覆蓋 Production

先分清楚 Release 與 Deployment
這兩個常常被放在一起講,但其實概念不同。
Release
比較像:
哪一份 Code 被正式認定為一個可交付版本?

例如:
SysAdminToolkit
Version 1.2.0

我們會把它:
Commit
↓
Tag v1.2.0
↓
Package

Deployment
則是:
把這個已經確認的 Release 安裝到哪個環境?

例如:
v1.2.0
↓
TEST-AUTO01

確認
↓
PROD-AUTO01

所以:
Release
≠
Deployment

同一個 Release:
v1.2.0

可能先部署:
Test

再部署:
Production

不要用「現在 Git main 的內容」直接當 Release
假設:
main

今天有:
Commit A
Commit B
Commit C

CI 都 Pass。
但 Production 到底跑:
A?
B?
C?

如果沒有明確 Version,很快就會混亂。
所以 Day 24 已經建立:
ModuleVersion = '1.0.0'

Day 26 又有:
git tag v1.0.0

今天要讓這兩個概念正式對應。
例如準備 Release 1.2.0
Manifest:
@{

RootModule =
    'SysAdminToolkit.psm1'

ModuleVersion =
    '1.2.0'

Author =
    'IT Operations'

PowerShellVersion =
    '5.1'

}

先確認:
Test-ModuleManifest `
.\Modules\SysAdminToolkit\SysAdminToolkit.psd1

取得:
$Manifest =
Test-ModuleManifest `
.\Modules\SysAdminToolkit\SysAdminToolkit.psd1

確認:
$Manifest.Version

應該:
1.2.0

CI Pass 後再建立 Git Tag
例如:
git status

先確認:
working tree clean

再看:
git log --oneline -5

確認正式 Release Commit。
然後:
git tag -a v1.2.0 -m "SysAdminToolkit 1.2.0"

查看:
git tag

可能:
v1.0.0
v1.1.0
v1.2.0

這代表:
v1.2.0 永遠指向這次 Release 的那個 Commit。

Tag 不應該亂移
假設:
v1.2.0

已經部署 Production。
之後發現 Bug。
不應該:
修改 Code
↓
把 v1.2.0 偷偷改指向新的 Commit

因為這會造成:
昨天的 v1.2.0
≠
今天的 v1.2.0

Version 就失去意義。
比較正確:
v1.2.0
有 Bug

↓ 修正

v1.2.1

也就是:
已發布版本應該視為 Immutable。

接下來建立 Release Package
現在 Module:
Modules/
└── SysAdminToolkit/
├── SysAdminToolkit.psd1
├── SysAdminToolkit.psm1
├── Public/
└── Private/

可以把它 Package 成:
SysAdminToolkit-1.2.0.zip

例如建立:
Artifacts/

然後:
$Version =
"1.2.0"

$Source =
".\Modules\SysAdminToolkit"

$ArtifactFolder =
".\Artifacts"

if (-not (Test-Path $ArtifactFolder)) {

New-Item `
    -Path $ArtifactFolder `
    -ItemType Directory |
    Out-Null

}

$Package =
Join-Path $ArtifactFolder
"SysAdminToolkit-$Version.zip"

接著:
Compress-Archive -Path "$Source\*"
-DestinationPath $Package `
-Force

現在:
Artifacts/
└── SysAdminToolkit-1.2.0.zip

為什麼不要直接 Copy Repository?
因為 Repository 裡還有:
Tests
Docs
Jenkinsfile
.github
Reports
Examples
Git Metadata

Production Automation Server 真正執行需要的可能只有:
SysAdminToolkit

Release Package 應該盡量:
只包含真正需要部署的內容。

這叫:
Build Artifact

Artifact 是什麼?
在今天的情境:
Source Code
↓
CI
↓
Package
↓
SysAdminToolkit-1.2.0.zip

這個 Zip 就是:
Artifact
後面 Test 與 Production 應該盡量部署:
同一份 Artifact。

而不是:
Test
→ 從 Git Copy 一次

Production
→ 三天後再從 Git Copy 一次

因為三天之間 Repository 可能已經改變。
Build Once,Deploy Many
我會偏好這個概念:
Release v1.2.0
↓
Build 一次
↓
SysAdminToolkit-1.2.0.zip
↓
├── Test
└── Production

也就是:
同一份 Package 從 Test 往 Production 推。

這樣比較容易回答:
Production 跑的真的是測試過的那份嗎?

加入 SHA256 Hash
Package 建好之後,可以:
Get-FileHash -Path $Package
-Algorithm SHA256

例如:
Algorithm Hash


SHA256 A1B2C3D4...

可以保存:
$Hash =
Get-FileHash -Path $Package
-Algorithm SHA256

再:
$Hash.Hash |
Set-Content `
"$Package.sha256"

最後:
Artifacts/
├── SysAdminToolkit-1.2.0.zip
└── SysAdminToolkit-1.2.0.zip.sha256

Hash 可以做什麼?
主要幫助我們確認:
Build 出來的 Package

跟:
要部署的 Package

是不是同一份。
例如 Build:
SHA256
ABC123...

部署前重新算:
(Get-FileHash $Package -Algorithm SHA256).Hash

應該還是:
ABC123...

如果不同:
Package 已經不是原本 Build 出來的那一份。

Production 不要直接覆蓋現有 Version
假設現在 Production:
SysAdminToolkit 1.1.0

如果直接:
1.2.0
↓
覆蓋 1.1.0

遇到問題時:
舊版本去哪了?

所以更安全的做法是:
Side-by-Side Versioning
例如:
C:\Program Files\WindowsPowerShell\Modules
└── SysAdminToolkit
├── 1.0.0
├── 1.1.0
└── 1.2.0\

每一個版本自己完整存在。
Version Folder 裡
例如:
SysAdminToolkit/
└── 1.2.0/
├── SysAdminToolkit.psd1
├── SysAdminToolkit.psm1
├── Public/
└── Private/

這種架構最大的好處是:
新版部署不需要先破壞舊版。

不要 Deployment 一開始就丟進正式資料夾
比較安全的 Deployment:
Package
↓
Staging
↓
Validation
↓
正式 Version Folder

例如:
C:\AutomationDeployment
├── Staging
└── Packages\

先解壓:
$Package =
"C:\AutomationDeployment\Packages\SysAdminToolkit-1.2.0.zip"

$Staging =
"C:\AutomationDeployment\Staging\SysAdminToolkit-1.2.0"

清掉舊 Staging:
if (Test-Path $Staging) {

Remove-Item `
    $Staging `
    -Recurse `
    -Force

}

建立:
New-Item -Path $Staging
-ItemType Directory `
-Force |
Out-Null

再:
Expand-Archive -Path $Package
-DestinationPath $Staging `
-Force

在 Staging 先 Validation
Manifest:
$StagingManifest =
Join-Path $Staging
"SysAdminToolkit.psd1"

先:
$Manifest =
Test-ModuleManifest -Path $StagingManifest
-ErrorAction Stop

確認 Version:
if (
$Manifest.Version.ToString() -ne "1.2.0"
) {

throw `
    "Package version mismatch."

}

這一步很重要。
因為:
檔名
SysAdminToolkit-1.2.0.zip

不代表裡面的:
ModuleVersion

真的就是:
1.2.0

再做 Import Smoke Test
從 Staging:
Import-Module $StagingManifest
-Force `
-ErrorAction Stop

確認:
Get-Command `
-Module SysAdminToolkit

例如檢查必要 Command:
$RequiredCommands = @(
"Get-ServerHealth"
"Get-HealthEventType"
"Write-Log"
"Save-HealthState"
)

逐個:
foreach ($Command in $RequiredCommands) {

if (
    -not (
        Get-Command `
            $Command `
            -Module SysAdminToolkit `
            -ErrorAction SilentlyContinue
    )
) {

    throw `
        "Missing command: $Command"
}

}

Staging 都正常之後,才準備 Deploy。
Deploy 到 Version Folder
例如:
$ModuleRoot =
"C:\Program Files\WindowsPowerShell\Modules\SysAdminToolkit"

$TargetVersion =
Join-Path $ModuleRoot
"1.2.0"

先確認:
if (Test-Path $TargetVersion) {

throw `
    "Version 1.2.0 already exists."

}

為什麼我反而不:
存在就 Force 覆蓋?

因為:
已發布的 Version Folder 也應該盡量視為不可修改。

如果:
1.2.0

已經存在,
應該先確認:
它是不是原本那一版?

而不是偷偷覆蓋。
Copy Staging 到 Target
例如:
New-Item -Path $TargetVersion
-ItemType Directory `
-Force |
Out-Null

Copy-Item -Path "$Staging\*"
-Destination $TargetVersion -Recurse
-ErrorAction Stop

現在:
SysAdminToolkit/
├── 1.1.0/
└── 1.2.0/

兩版同時存在。
但 Production 到底 Import 哪一版?
這就非常重要了。
如果 Production Script 只是:
Import-Module SysAdminToolkit

PowerShell Module Discovery 可能選擇可用版本中的適當版本,通常會偏向較新的版本。
但對 Production Automation,我反而希望:
版本是明確指定的。

例如:
Import-Module SysAdminToolkit
-RequiredVersion "1.1.0"

現在即使:
1.2.0

已經部署到 Server,
Production 仍然:
跑 1.1.0

直到我們正式切換。
這就是 Deploy 跟 Activate 分離
流程:
Deploy 1.2.0
↓
Version Folder 已存在
↓
Production 還在 1.1.0
↓
Smoke Test 1.2.0
↓
Approval
↓
Activate 1.2.0

這比:
Copy 完

立刻 Production

安全很多。
Version 可以放 Configuration
例如:
Config/
└── Production.psd1

內容:
@{

SysAdminToolkitVersion =
    '1.1.0'

}

Main Script:
$Config =
Import-PowerShellDataFile `
"$PSScriptRoot..\Config\Production.psd1"

再:
Import-Module SysAdminToolkit
-RequiredVersion $Config.SysAdminToolkitVersion
-ErrorAction Stop

切換 Production Version
完成測試與 Approval 後,
把:
SysAdminToolkitVersion =
'1.1.0'

改成:
SysAdminToolkitVersion =
'1.2.0'

這一個動作才是:
Activation
也就是:
新版已部署
+
Production 正式開始使用新版

為什麼 Version Pinning 很重要?
假設 Server:
1.1.0
1.2.0
1.3.0

如果 Script:
Import-Module SysAdminToolkit

半年後有人部署:
1.4.0

結果所有 Script 下次執行突然開始使用 1.4.0。
這會造成:
部署一個 Module,就同時改變很多 Production Job。

如果 Pin:
-RequiredVersion 1.2.0

就不會。
每個 Workflow 都可以明確知道:
我正在跑哪一版。

Log 也要記 Version
Day 20 已經建立:
Write-Log

現在 Automation 開始時可以:
$Toolkit =
Get-Module `
SysAdminToolkit

Write-Log -Path $LogFile
-Message "SysAdminToolkit Version: $($Toolkit.Version)"

Log:
2026-10-06 06:00:00 [INFO]
SysAdminToolkit Version: 1.2.0

以後發生 Incident:
10/06 早上 Report 為什麼怪怪的?

第一件事情就可以看到:
當時跑的是 1.2.0。

Deployment 也需要 Log
不要只記:
Application Runtime Log

Deployment 本身也應該留下紀錄。
例如:
2026-10-06 05:00:00
Release : 1.2.0
Server : PROD-AUTO01
Operator : CONTOSO\admin01
Package : SysAdminToolkit-1.2.0.zip
Hash : ABC123...
Result : Success

這樣才能回答:
什麼時候部署?
部署哪一版?
誰做的?
Package 是哪一份?
最後成功嗎?

建立 Deployment Result Object
例如:
$DeploymentResult =
[PSCustomObject]@{

    ComputerName =
        $env:COMPUTERNAME

    ModuleName =
        "SysAdminToolkit"

    Version =
        "1.2.0"

    Package =
        $Package

    PackageHash =
        (
            Get-FileHash `
                $Package `
                -Algorithm SHA256
        ).Hash

    Operator =
        [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

    DeploymentTime =
        Get-Date

    Result =
        "Success"
}

輸出:
$DeploymentResult |
Export-Csv "C:\AutomationDeployment\Logs\DeploymentHistory.csv"
-Append -NoTypeInformation
-Encoding UTF8

這就是:
Deployment Audit Trail。

Production Smoke Test
新版已經部署後,
不要直接等隔天 06:00 才知道能不能用。
至少做:
Import-Module SysAdminToolkit
-RequiredVersion "1.2.0" -Force
-ErrorAction Stop

確認 Version:
(Get-Module SysAdminToolkit).Version

應該:
1.2.0

再:
Get-Command `
-Module SysAdminToolkit

更好一點可以跑 Safe Smoke Check
例如:
Get-HealthEventType -PreviousStatus "Healthy"
-CurrentStatus "Critical"

預期:
NewAlert

這種沒有 Production Change 的 Function 非常適合。
不要用 Offboarding 當 Smoke Test
Production 部署完不要:
測一下 Disable-ADAccount 能不能用

Smoke Test 應該:
Safe
Read-only
可重複

例如:
Import Module
Function Exists
Manifest Correct
Pure Logic Function
Read-only Test Server Query

而不是:
建立帳號
刪 Group
停用 User

如果新版有問題怎麼辦?
這時 Side-by-Side 的價值就出現。
目前:
1.1.0
1.2.0

如果:
Production
1.2.0

↓ 發現 Bug

不需要:
趕快找舊 Zip
覆蓋回去

只需要把 Production Version:
SysAdminToolkitVersion =
'1.2.0'

切回:
SysAdminToolkitVersion =
'1.1.0'

再執行:
Import-Module SysAdminToolkit
-RequiredVersion "1.1.0" `
-Force

這就是:
Rollback
Rollback 不代表 Delete 新版
發現:
1.2.0

有問題,
第一步通常不是:
rm -r 1.2.0

而是:
停止使用 1.2.0
↓
Production 回 1.1.0
↓
保存證據
↓
分析 Bug
↓
1.2.1 修正

這樣:
有問題的 Release

仍然能被保留下來分析。
不要直接修改 Production 裡的 1.2.0
假設 Production 發現:
Get-HealthEventType
有 Bug

不要 SSH / RDP 上去:
Notepad
↓
直接改
SysAdminToolkit\1.2.0*.ps1

因為這樣會造成:
Production 1.2.0
≠
Git Tag v1.2.0
≠
Release Artifact 1.2.0

Version 完全失去可信度。
比較好的流程:
Bug Found
↓
Git Branch
↓
Fix Code
↓
Pester
↓
CI
↓
Release 1.2.1
↓
Deploy

也就是:
Production 不直接 Hot Edit。

如果真的有 Emergency Change,也應該把修改同步回 Version Control,並依公司緊急變更流程處理。
Release Package 也不能手工改
同理:
SysAdminToolkit-1.2.0.zip

Build 之後就不要:
解壓
改一個 PS1
重新壓縮
檔名還叫 1.2.0

因為 Hash 也會跟著變。
這就是為什麼我們今天加入:
Git Tag
Artifact
Hash

現在形成一條 Chain of Trust
可以理解成:
Git Commit
↓
Git Tag v1.2.0
↓
CI Passed
↓
Artifact
SysAdminToolkit-1.2.0.zip
↓
SHA256
↓
Deployment
↓
Version 1.2.0

每一步都可以追到前一步。
這就是:
Traceable Release。

Approval Gate 放在哪裡?
現在 CI:
PASS

不代表一定馬上:
Deploy Production

可以:
CI PASS
↓
Package
↓
Deploy Test
↓
Smoke Test
↓
Approval
↓
Deploy Production

例如:
Change Ticket
CHG001234

批准:
v1.2.0
2026-10-06
22:00 Change Window

才進 Production。
哪些情況適合自動 Deployment?
例如:
測試環境
Development Environment
低風險工具
Read-only Report Tool

可以高度自動化。
但:
AD Account Lifecycle
Domain Operations
高權限 Automation

可能仍然需要:
Human Approval

所以:
CI/CD 不代表所有 Production Change 都必須完全無人值守。

Jenkins 可以做 Approval Gate
概念例如:
Stage 1
Test
↓
Stage 2
Package
↓
Stage 3
Deploy Test
↓
Stage 4
Approval
↓
Stage 5
Deploy Production

Jenkins Pipeline 概念:
stage('Approval') {

steps {

    input message:
        'Deploy SysAdminToolkit to Production?'

}

}

然後才:
Deploy Production

GitHub 環境也可以有 Environment Protection
概念上:
CI
↓
Artifact
↓
Production Environment
↓
Required Approval
↓
Deployment

但實際公司要使用哪個機制,還是看:
Git 平台
權限架構
資安政策
Change Process

今天重點是:
Approval 應該存在於流程中,而不是靠群組說一句「你可以上了」。

建立 Release Script
例如:
Scripts/
└── Build-Release.ps1

內容可以:

==========================================

Build SysAdminToolkit Release

==========================================

$ErrorActionPreference =
"Stop"

$ProjectRoot =
Split-Path $PSScriptRoot
-Parent

$ModuleRoot =
Join-Path $ProjectRoot
"Modules\SysAdminToolkit"

$ManifestPath =
Join-Path $ModuleRoot
"SysAdminToolkit.psd1"

$ArtifactFolder =
Join-Path $ProjectRoot
"Artifacts"

==========================================

Manifest

==========================================

$Manifest =
Test-ModuleManifest -Path $ManifestPath
-ErrorAction Stop

$Version =
$Manifest.Version.ToString()

Write-Host `
"Building SysAdminToolkit $Version"

==========================================

Artifact Folder

==========================================

if (-not (Test-Path $ArtifactFolder)) {

New-Item `
    -Path $ArtifactFolder `
    -ItemType Directory |
    Out-Null

}

$Package =
Join-Path $ArtifactFolder
"SysAdminToolkit-$Version.zip"

if (Test-Path $Package) {

throw `
    "Artifact already exists: $Package"

}

==========================================

Package

==========================================

Compress-Archive -Path "$ModuleRoot\*"
-DestinationPath $Package `
-ErrorAction Stop

==========================================

SHA256

==========================================

$Hash =
Get-FileHash -Path $Package
-Algorithm SHA256

$HashFile =
"$Package.sha256"

$Hash.Hash |
Set-Content -Path $HashFile
-Encoding ASCII

==========================================

Result

==========================================

Write-Host ""
Write-Host "Release created."

Write-Host `
"Version : $Version"

Write-Host `
"Package : $Package"

Write-Host `
"SHA256 : $($Hash.Hash)"

Build Script 不應該幫你決定 Version
Version 應該來自:
SysAdminToolkit.psd1

而不是:
$Version = "1.2.0"

到處寫死。
這樣:
Manifest

就是 Module Version 的主要來源。
Deployment Script
例如:
Scripts/
└── Install-SysAdminToolkit.ps1

可以設計:
param (

[Parameter(Mandatory)]
[string]
$Package,

[Parameter(Mandatory)]
[string]
$ExpectedHash

)

先驗證:
$ActualHash =
(
Get-FileHash -Path $Package
-Algorithm SHA256
).Hash

if (
$ActualHash -ne
$ExpectedHash
) {

throw `
    "SHA256 validation failed."

}

再 Staging
$DeploymentRoot =
"C:\AutomationDeployment"

$Staging =
Join-Path $DeploymentRoot
"Staging"

解壓後:
$ManifestPath =
Join-Path $Staging
"SysAdminToolkit.psd1"

驗證:
$Manifest =
Test-ModuleManifest $ManifestPath
-ErrorAction Stop

Version:
$Version =
$Manifest.Version.ToString()

再放入 Module Folder
$ModuleBase =
"C:\Program Files\WindowsPowerShell\Modules\SysAdminToolkit"

$Target =
Join-Path $ModuleBase
$Version

確認不存在:
if (Test-Path $Target) {

throw `
    "Version already deployed: $Version"

}

再:
Copy-Item -Path $Staging
-Destination $Target -Recurse
-ErrorAction Stop

實際打包結構不同時,Copy-Item 的來源層級需要依 Artifact 內容調整;正式使用前要先在 Test Server 驗證最後目錄確實是:
SysAdminToolkit
└── 1.2.0
├── SysAdminToolkit.psd1
└── SysAdminToolkit.psm1

避免多包一層:
1.2.0
└── SysAdminToolkit
└── SysAdminToolkit.psd1

導致 PowerShell 找不到 Module。
Deployment 後重新驗證
$InstalledManifest =
Join-Path $Target
"SysAdminToolkit.psd1"

Test-ModuleManifest $InstalledManifest
-ErrorAction Stop |
Out-Null

再:
Import-Module SysAdminToolkit
-RequiredVersion $Version -Force
-ErrorAction Stop

確認:
$LoadedVersion =
(
Get-Module `
SysAdminToolkit
).Version.ToString()

如果:
$LoadedVersion -ne $Version

就:
throw `
"Loaded version mismatch."

Release / Deployment Result 不要只顯示 Success
建議至少記錄:
ReleaseVersion
Package
PackageHash
TargetServer
TargetPath
ManifestValidation
ImportTest
SmokeTest
DeploymentResult
DeploymentTime
Operator

這些資訊很適合未來稽核與 Troubleshooting。
CI 也可以產 Artifact
Day 27 的 Pipeline:
Validate
↓
Pester

今天可以加:
Validate
↓
Test
↓
Package
↓
Publish Artifact

例如:
CI Build #108

Artifact:
SysAdminToolkit-1.2.0.zip

Production Deployment 就使用:
Build #108 的 Artifact

而不是:
去 Git 找一份「看起來差不多」的 Code。

CI 與 CD 到今天正式分開
現在可以很清楚看到:
CI
Code Change
↓
Manifest Validation
↓
Pester
↓
Quality Gate
↓
Build Artifact

Delivery / Deployment
Artifact
↓
Test
↓
Approval
↓
Deploy Version
↓
Smoke Test
↓
Activate

這就是前面問過:
CI 跟 CD 到底差在哪?

到 Day 28 已經可以用我們自己做的專案直接回答了。
Production 不一定要「自動切新版」
我們甚至可以做到:
CI
↓
Artifact 1.2.0
↓
自動部署到 Production Server
↓
1.2.0 Folder 已存在
↓
但 Production Config 還是 1.1.0

真正切換:
1.1.0
↓
1.2.0

仍然需要:
Approval

這樣做到:
Deployment Automation + Controlled Activation

我會比:
Build 完馬上全部自動切 Production

更放心。
Rollback 流程也應該先想好
不要出事時才問:
現在怎麼辦?

Release 前就應該知道:
Current
1.1.0

Target
1.2.0

Rollback
1.1.0

也就是:
Deploy 1.2.0
↓
Smoke Test
↓
Activate 1.2.0
↓
Monitoring
↓
異常?
├── No → Continue
└── Yes
↓
Switch 1.1.0

Rollback 後仍然要 Verification
切回:
1.1.0

並不代表一定成功。
仍然要:
Import-Module SysAdminToolkit
-RequiredVersion "1.1.0" -Force
-ErrorAction Stop

確認:
(Get-Module SysAdminToolkit).Version

再執行安全 Smoke Test。
所以:
Rollback 本身也是一次 Change。

一樣要:
Execute
↓
Verify
↓
Log

不要無限保留所有版本
Side-by-Side 很方便。
但如果:
1.0.0
1.0.1
1.0.2
...
3.9.7

幾年後可能非常多。
所以 Production 可以有 Retention Policy,例如:
Current Version
Previous Stable Version
最近幾個 Release

更老的 Version 保存在:
Artifact Repository
Git Tag
Release Storage

而不需要全部留在 Automation Server。
Artifact Repository
小型環境可以先:
\FILESERVER\AutomationReleases\

例如:
AutomationReleases/
└── SysAdminToolkit/
├── 1.0.0/
├── 1.1.0/
└── 1.2.0/

更成熟可以使用:
Azure Artifacts
GitHub Releases
GitLab Package Registry
Nexus
Artifactory
Internal PowerShell Repository

但概念都是:
正式 Release 應該有一個集中、可追蹤的地方。

PowerShell Module 甚至可以做 Internal Repository
未來如果公司 PowerShell Automation 越來越多,可以建立內部 Repository。
那使用方式可能變:
Install-Module SysAdminToolkit
-RequiredVersion 1.2.0 `
-Repository CompanyPSRepo

升級:
Update-Module `
SysAdminToolkit

甚至:
Publish-Module

但這已經是更進一步的 Package Management。
今天先把:
Version
Artifact
Validation
Deployment
Rollback

觀念做好。
現在完整 Release Flow
我們的流程可以畫成:
Developer
│
▼
Feature Branch
│
▼
PowerShell Code
│
▼
Pester
│
▼
Git Commit
│
▼
Pull Request
│
▼
CI
│
├── Manifest
├── Import
├── Smoke Test
└── Pester
│
▼
Quality Gate
│
▼
Merge Main
│
▼
ModuleVersion Update
│
▼
Git Tag
│
▼
Build Artifact
│
▼
SHA256
│
▼
Test Deployment
│
▼
Smoke Test
│
▼
Approval
│
▼
Production Deployment
│
▼
Side-by-Side Version
│
▼
Activate Version
│
▼
Verification
│
▼
Monitoring

這已經不是單純:
Copy PowerShell Script

而是一個完整:
Release Pipeline。

現在專案結構再更新
到 Day 28:
SysAdmin-Automation/
│
├── .git/
├── .gitignore
│
├── Modules/
│ └── SysAdminToolkit/
│ ├── SysAdminToolkit.psd1
│ ├── SysAdminToolkit.psm1
│ ├── Public/
│ └── Private/
│
├── Scripts/
│ ├── Collect-ServerHealth.ps1
│ ├── New-DailyHtmlReport.ps1
│ ├── Send-HealthAlert.ps1
│ ├── Invoke-CITests.ps1
│ ├── Build-Release.ps1
│ └── Install-SysAdminToolkit.ps1
│
├── Tests/
│ ├── Module.Tests.ps1
│ ├── Get-HealthEventType.Tests.ps1
│ └── ...
│
├── Config/
│ └── Production.psd1
│
├── Artifacts/
│ ├── SysAdminToolkit-1.2.0.zip
│ └── SysAdminToolkit-1.2.0.zip.sha256
│
├── Reports/
├── Logs/
├── State/
│
├── Jenkinsfile
├── README.md
└── CHANGELOG.md

現在真的開始有一個:
可開發、可測試、可發布、可部署的 Automation Project。

Day 28 小結
今天解決的是:
CI 通過以後,Code 怎麼安全進 Production?

我們沒有使用:
CI Passed
↓
直接覆蓋 Production

而是建立:
CI
↓
Release
↓
Artifact
↓
Validation
↓
Deployment
↓
Activation

第一個重要觀念是:
Release 與 Deployment 不完全相同。

Release
→ 定義可交付版本

Deployment
→ 把版本放進目標環境

第二個是:
Build Once,Deploy Many。

同一份:
SysAdminToolkit-1.2.0.zip

應該盡量從:
Test

一路推到:
Production

而不是每個環境重新組一份。
第三個是:
正式 Release 應該 Immutable。

v1.2.0

發布後有 Bug:
不要偷偷改 1.2.0

應該:
1.2.1

第四個則是:
Production Module 最好 Side-by-Side。

例如:
SysAdminToolkit
├── 1.1.0
└── 1.2.0

不要每次:
新版直接把舊版蓋掉。

第五個:
Deployment 跟 Activation 可以分離。

先:
Deploy 1.2.0

但 Production 還:
Pinned 1.1.0

確認後再:
Activate 1.2.0

最後則是:
Rollback 不是事後才想,而是 Release 前就要設計。

例如:
Current = 1.1.0
Target = 1.2.0
Rollback = 1.1.0

現在整個工程流程已經成為:
PowerShell
↓
Module
↓
Pester
↓
Git
↓
CI
↓
Release
↓
Artifact
↓
Deploy
↓
Verify
↓
Production

我們已經從最開始:
Get-Service

走到了:
PowerShell Automation Software Delivery。

這也是 System Engineer 往:
Automation Engineer
DevOps
Platform Engineer
SRE

發展時非常重要的一段能力。
Day 29 預告
Day 29|把 29 天工具真正串起來:建立 Daily Automation Pipeline 與 End-to-End Workflow
現在我們其實已經有非常多零件:
Server Health Check
AD Audit
CSV Config
Remoting
Error Handling
Logging
Task Scheduler
HTML Report
Notification
State Tracking
Module
Pester
Git
CI
Release
Deployment

但是目前很多內容還是:
一個功能
一支 Script
一個概念

所以 Day 29 不會再新增一大堆新技術。
Day 29 我們會做最後一次實戰整合:
Task Scheduler
↓
DailyAutomation.ps1
↓
Load Config
↓
Import SysAdminToolkit
↓
Server Health
↓
AD Audit
↓
State Comparison
↓
CSV
↓
HTML
↓
Notification
↓
Log
↓
Exit Code

並且整理:
Fatal Failure
Partial Failure
No Data
Warning
Critical
Recovery
Notification Failure

到底整個 Workflow 應該怎麼處理。
也就是把前面分散的能力正式組成:
End-to-End SysAdmin Automation Pipeline


上一篇
Day 27|PowerShell CI:Git Commit 之後自動跑 Pester,不讓壞掉的 Module 進入下一步
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言