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
立刻 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
內容可以:
$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 =
Test-ModuleManifest -Path $ManifestPath
-ErrorAction Stop
$Version =
$Manifest.Version.ToString()
Write-Host `
"Building SysAdminToolkit $Version"
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"
}
Compress-Archive -Path "$ModuleRoot\*"
-DestinationPath $Package `
-ErrorAction Stop
$Hash =
Get-FileHash -Path $Package
-Algorithm SHA256
$HashFile =
"$Package.sha256"
$Hash.Hash |
Set-Content -Path $HashFile
-Encoding ASCII
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