GCP MIG 重建后 Ansible 初始化实战:PowerShell + NSSM 挂载 Windows 服务
在 GCP 托管实例组(MIG)自动修复或重建 Windows 实例后,如何确保新实例自动完成系统初始化,并用 NSSM 把 PowerShell 脚本挂载为常驻服务?本文给出从 MIG 延迟窗口配置到 Ansible Playbook 调用、再到幂等 PowerShell 脚本的完整落地方案。
背景与问题
GCP 的 MIG 在实例不健康时会自动重建 VM,但重建出来的新实例并不会自动执行你自定义的 Ansible 初始化流程。如果服务依赖 NSSM 挂载的 PowerShell 进程,而该进程尚未启动,健康检查就会失败,进而触发又一次重建,形成循环。
解决思路分两层:MIG 侧利用 initial_delay_sec 给初始化留出窗口期 ;Ansible 侧通过动态清单发现新实例,执行 PowerShell 脚本完成 NSSM 服务挂载。
一、MIG 侧:配置初始延迟窗口
MIG 的自动修复策略中有一个关键参数 initial_delay_sec。官方文档说明:这是新 VM 初始化并运行启动脚本所需的秒数,在此期间 MIG 会忽略不成功的健康检查,防止过早重建实例。取值范围 0 到 3600 秒,控制台默认值通常为 300 秒。
实操建议 :先手动计时一次完整的 Ansible 初始化流程(上传脚本 → 安装 NSSM → 创建服务 → 启动),然后在此基础上加 30%~50% 的缓冲。例如实测耗时 4 分钟,可以设 initial_delay_sec 为 360。设置过短会导致 MIG 在服务就绪前就判定实例不健康,引发反复重建。
二、Ansible 侧:发现新实例并执行初始化
MIG 重建的实例 IP 会变化,静态 inventory 不可靠。推荐使用 GCP 动态清单插件(google.cloud.gcp_compute),基于实例标签(Labels)自动发现当前所有匹配的 Windows 主机。在实例模板中为 VM 打上统一标签(如 role=app-worker),Ansible 即可自动抓取新重建的实例。
Playbook 的核心任务分为三步:上传 PowerShell 脚本、执行脚本、验证服务状态。其中执行脚本使用 ansible.windows.win_shell,该模块默认通过 PowerShell 运行命令,适合调用 .ps1 文件。
yaml
- name: GCP MIG Windows 实例初始化
hosts: windows_mig_instances
tasks:
- name: 确保 NSSM 已安装
chocolatey.chocolatey.win_chocolatey:
name: nssm
state: present
- name: 创建应用目录
ansible.windows.win_file:
path: C:\App\logs
state: directory
- name: 上传 NSSM 初始化脚本
ansible.windows.win_copy:
src: files/setup-nssm-service.ps1
dest: C:\App\setup-nssm-service.ps1
- name: 执行服务安装
ansible.windows.win_shell: |
powershell.exe -ExecutionPolicy Bypass -File C:\App\setup-nssm-service.ps1 `
-Action install `
-ServiceName "MyAppService" `
-ScriptPath "C:\App\worker.ps1"
register: install_result
- name: 验证服务运行状态
ansible.windows.win_service:
name: MyAppService
state: started
register: svc_check
- name: 输出初始化结果
ansible.builtin.debug:
msg: "服务状态: {{ svc_check.state }}"
为什么不用 community.windows.win_nssm 模块? 该模块确实可以直接创建 NSSM 服务,但它有一个关键限制:state=present 时服务创建后不会自动启动 ,需要额外配合 ansible.windows.win_service 来启动。如果你的场景只需要简单挂载且不涉及复杂日志轮转、环境变量等配置,用模块更简洁。但本文的场景需要控制日志重定向、自动重启策略等细节,用自定义 PowerShell 脚本封装更灵活。
三、核心脚本:setup-nssm-service.ps1
该脚本设计为幂等 ,支持 install / uninstall / status 三种操作,适合在 MIG 重建场景下反复执行。
powershell
<#
.SYNOPSIS
使用 NSSM 将 PowerShell 脚本挂载为 Windows 服务(幂等)
.DESCRIPTION
供 Ansible 在 GCP MIG 重建后调用。
.PARAMETER Action
install / uninstall / status
#>
param(
[Parameter(Mandatory=$true)]
[ValidateSet("install","uninstall","status")]
[string]$Action,
[string]$ServiceName = "MyAppService",
[string]$ScriptPath = "C:\App\worker.ps1",
[string]$LogDir = "C:\App\logs",
[string]$DisplayName = "My Application Worker",
[string]$Description = "由 Ansible 通过 NSSM 部署的后台任务"
)
$ErrorActionPreference = "Stop"
function Assert-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "此脚本必须以管理员身份运行"
}
}
function Get-NssmPath {
$cmd = Get-Command nssm.exe -ErrorAction SilentlyContinue
if (-not $cmd) { throw "nssm.exe 未找到,请先 choco install nssm" }
return $cmd.Source
}
function Test-ServiceExists($Name) {
return ($null -ne (Get-Service -Name $Name -ErrorAction SilentlyContinue))
}
Assert-Admin
$nssm = Get-NssmPath
switch ($Action) {
"install" {
if (-not (Test-Path $ScriptPath)) { throw "脚本不存在: $ScriptPath" }
if (-not (Test-Path $LogDir)) {
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
}
$psExe = (Get-Command powershell.exe).Source
$args = "-ExecutionPolicy Bypass -NoProfile -File `"$ScriptPath`""
if (-not (Test-ServiceExists $ServiceName)) {
& $nssm install $ServiceName $psExe $args
} else {
& $nssm set $ServiceName Application $psExe
& $nssm set $ServiceName AppParameters $args
}
& $nssm set $ServiceName Start SERVICE_AUTO_START
& $nssm set $ServiceName DisplayName $DisplayName
& $nssm set $ServiceName Description $Description
& $nssm set $ServiceName AppStdout (Join-Path $LogDir "$ServiceName-stdout.log")
& $nssm set $ServiceName AppStderr (Join-Path $LogDir "$ServiceName-stderr.log")
& $nssm set $ServiceName AppRotateFiles 1
& $nssm set $ServiceName AppRotateSeconds 86400
& $nssm set $ServiceName AppRestartDelay 5000
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($svc.Status -ne "Running") {
& $nssm start $ServiceName
}
Write-Host "完成。服务状态: $((Get-Service $ServiceName).Status)"
}
"uninstall" {
if (-not (Test-ServiceExists $ServiceName)) {
Write-Host "服务不存在: $ServiceName"; return
}
& $nssm stop $ServiceName 2>$null
Start-Sleep -Seconds 2
& $nssm remove $ServiceName confirm
Write-Host "已卸载: $ServiceName"
}
"status" {
if (-not (Test-ServiceExists $ServiceName)) {
Write-Host "服务不存在: $ServiceName"; exit 1
}
Get-Service -Name $ServiceName | Format-List Name, Status, StartType, DisplayName
}
}
脚本设计要点
幂等性:安装前检测服务是否存在,存在则只更新配置,不重复创建。MIG 重建或 Ansible 重跑都不会报错。
日志轮转 :AppRotateFiles 1 配合 AppRotateSeconds 86400 实现每日日志切分,避免日志文件无限膨胀。AppRestartDelay 5000 让服务崩溃 5 秒后自动重启,这对无人值守的 MIG 实例尤为关键。
账户说明 :脚本默认以 LocalSystem 运行服务。如果 worker.ps1 需要访问网络资源(如域 API、UNC 路径),需要在脚本中追加 nssm set <服务名> ObjectName "DOMAIN\User" "password" 来切换运行身份。敏感密码建议通过 Ansible Vault 传入,而不是硬编码在脚本里。
四、避坑清单
坑 1:initial_delay_sec 设得太短。这是 MIG + Ansible 组合最容易翻车的地方。如果延迟只有 60 秒,而 Ansible 安装 NSSM + 创建服务 + 启动需要 3 分钟,VM 会在服务就绪前被判定不健康而重建,形成死循环。务必先实测再设定。
坑 2:NSSM 不在 PATH 中 。脚本通过 Get-Command nssm.exe 定位二进制文件。虽然 Playbook 中先用 Chocolatey 安装了 NSSM,但 Chocolatey 安装后可能需要刷新环境变量。稳妥做法是在 Playbook 的 win_chocolatey 任务后加一个 win_shell 执行 refreshenv(如果目标机有 Chocolatey 的 refreshenv 函数),或者让 Playbook 使用绝对路径调用 NSSM。
坑 3:win_shell 无法启动后台进程 。Ansible 官方文档特别指出,win_shell 会等待所有子进程退出后才返回,因此不适合直接用来 spawn 长时间运行的进程。这正是我们需要 NSSM 的原因:把长时任务注册为 Windows 服务,由服务管理器托管,Ansible 只负责"安装服务"这个短时操作。
坑 4:服务创建后忘了启动 。如果你选择用 community.windows.win_nssm 模块而不是自定义脚本,务必记得模块的 state=present 不会自动启动服务 ,需要紧跟一个 ansible.windows.win_service 任务来 state: started。
总结
整个方案的执行链路是:MIG 重建实例 → initial_delay_sec 窗口期内健康检查被忽略 → Ansible 动态清单发现新 VM → 上传并执行 setup-nssm-service.ps1 → NSSM 注册服务并启动 → 服务开始响应健康检查 → MIG 确认实例健康。
这套方案的关键在于把"服务托管"交给 NSSM、把"配置管理"交给 Ansible、把"重建容错"交给 MIG 的初始延迟,三者各司其职,避免在脚本里硬编码复杂的重试逻辑。
作者简介
Moshow,一名热爱技术与分享的 Technical Lead & 数据科学家 & SpringBoot 专家。CSDN 博客之星 2025 年度 TOP100,Github 4K stars。
持有多项权威认证,包括:GCA 谷歌云架构师认证、Neo4j 数据科学家认证、阿里云 ACP 认证、金蝶高级 CRM 供应链工程师认证、PMP 项目管理认证、CSPM 项目管理专业人员能力评价认证、广东省人工智能训练师认证。
