Ansible 批量部署 Node.js 服务:ZIP 直解与共享目录分发两种方案对比
在 Windows 服务器集群中批量更新 Node.js 代码,核心诉求是"停服务 → 覆盖代码 → 起服务",同时保留远程
node_modules不动。本文提供两种落地方案:方案一 用win_unzip直接解压覆盖,方案二 借助共享目录中转分发。两者各有适用场景,方案仅供参考,请根据自己的实际情况使用合适的方案和调整合适的playbook。
背景与前提
假设 zip 包中已确认不包含 node_modules ,因此无论哪种方案,远程服务器上已有的依赖目录都不会被触碰。服务通过 Windows 服务(NSSM 或其他方式)托管,服务名假设为 MyNodeApp,目标部署目录为 C:\App\my-node-project。
两种方案共享相同的"停止/启动服务"逻辑,差异在于代码文件如何到达目标机器并覆盖。
方案一:ZIP 直解覆盖(逐机传输)
适用场景
服务器数量较少(10 台以内)、zip 包体积不大(几十 MB 级别)、各机器网络互通性一般、不想额外搭建共享存储。
核心逻辑
Ansible 通过 win_copy 将 zip 包逐个上传到每台目标机,再用 community.windows.win_unzip 解压到部署目录。win_unzip 原生支持 zip 格式,解压时覆盖同名文件,不会删除目标目录中 zip 里不存在的旧文件。
Playbook 示例
yaml
---
- name: 方案一 - ZIP直解部署 Node.js 服务
hosts: windows_servers
vars:
service_name: "MyNodeApp"
app_dir: "C:\\App\\my-node-project"
zip_source: "files/node-app.zip"
remote_zip: "C:\\Temp\\node-app.zip"
tasks:
- name: 停止 Node.js 服务
ansible.windows.win_service:
name: "{{ service_name }}"
state: stopped
- name: 上传代码包到目标机
ansible.windows.win_copy:
src: "{{ zip_source }}"
dest: "{{ remote_zip }}"
- name: 解压代码包到应用目录(覆盖同名文件)
community.windows.win_unzip:
src: "{{ remote_zip }}"
dest: "{{ app_dir }}"
rm: true
- name: 启动 Node.js 服务
ansible.windows.win_service:
name: "{{ service_name }}"
state: started
关键注意点
win_unzip 不是幂等模块 。官方文档明确说明它"not really idempotent",每次执行都会解压并报告 changed。这意味着即使代码没变,Playbook 也会显示变更。如果你用 --check 做预检,结果会始终显示"将发生变更",这是预期行为,不影响实际正确性。
覆盖不等于同步 。win_unzip 只覆盖 zip 中存在的文件,不会删除目标目录里 zip 已移除的旧文件。如果你的版本迭代涉及删除文件 (比如废弃了某个 legacy.js),旧文件会残留在部署目录中。如果需要严格对齐目录内容,方案一不适用,或者需要在解压前额外执行清理逻辑(但保留 node_modules)。
大文件传输效率 。win_copy 通过 WinRM 传输,官方文档指出它"not a very efficient transfer mechanism",大文件场景下 base64 编码开销明显。如果 zip 包超过 50MB 且节点较多,方案一的耗时会显著上升。
方案二:共享目录分发(一次上传,多次拉取)
适用场景
服务器数量较多(几十台以上)、zip 包较大、所有目标机都能访问同一个 SMB 共享路径(如 \\fileserver\deploy$\)。核心思路是控制机只上传一次到共享目录,各目标机从共享目录拉取,避免 WinRM 逐机传输的重复开销。
核心逻辑
先用 win_copy 将 zip 包上传到共享目录(如果控制机可以直接写入共享路径,也可以用 ansible.builtin.copy 或 win_copy 的 remote_src 变体)。然后在每台目标机上,用 PowerShell 的 Copy-Item 从共享路径拉取 zip 到本地临时目录,再解压覆盖。
这里需要用到 win_copy 的 remote_src: true 参数,让 Ansible 在远程主机上执行复制,源路径和目标路径都在远程端,不经过控制机中转。
Playbook 示例
yaml
---
- name: 方案二 - 共享目录分发部署 Node.js 服务
hosts: windows_servers
vars:
service_name: "MyNodeApp"
app_dir: "C:\\App\\my-node-project"
share_zip: "\\\\fileserver\\deploy$\\node-app.zip"
local_zip: "C:\\Temp\\node-app.zip"
tasks:
- name: 停止 Node.js 服务
ansible.windows.win_service:
name: "{{ service_name }}"
state: stopped
- name: 从共享目录拉取代码包到本地
ansible.windows.win_copy:
src: "{{ share_zip }}"
dest: "{{ local_zip }}"
remote_src: true
- name: 解压代码包到应用目录(覆盖同名文件)
community.windows.win_unzip:
src: "{{ local_zip }}"
dest: "{{ app_dir }}"
rm: true
- name: 启动 Node.js 服务
ansible.windows.win_service:
name: "{{ service_name }}"
state: started
前置条件:上传 zip 到共享目录
方案二需要一个独立的准备步骤:先把 zip 包放到共享路径。这个步骤只执行一次,不依赖目标机组:
yaml
- name: 上传代码包到共享目录
hosts: localhost
tasks:
- name: 复制 zip 到 SMB 共享
ansible.builtin.copy:
src: "files/node-app.zip"
dest: "/mnt/deploy_share/node-app.zip"
# 假设控制机已挂载 SMB 共享到 /mnt/deploy_share
如果控制机是 Linux 且没有挂载 SMB,也可以用 win_copy 先传到一台"跳板"Windows 机器,再从那台机器复制到共享目录。具体方式取决于你的环境拓扑。
关键注意点
共享目录的访问权限。执行 Ansible 任务的账户必须对共享路径有读取权限。如果使用域账户运行 WinRM 连接,通常可以直接访问域内共享;如果是本地账户,需要在共享服务器上为该账户单独授权。
remote_src: true 的行为 。当 src 和 dest 都在远程主机上时,win_copy 不会经过控制机,直接在目标 Windows 机器内部完成文件复制。但注意 src 如果是 UNC 路径(\\server\share\file),实际上涉及网络 IO,速度取决于目标机到文件服务器的网络质量。
共享目录方案的"一次上传"优势只有在节点数足够多时才明显。如果只有 3 台机器,方案一的逐机上传和方案二的"上传一次 + 三次拉取"差别不大,反而方案二多了一个共享目录的维护成本。
选型建议
| 维度 | 方案一:ZIP 直解 | 方案二:共享目录 |
|---|---|---|
| 节点数量 | 少(≤10) | 多(>10) |
| ZIP 体积 | 小(<50MB) | 大(>50MB) |
| 网络要求 | 控制机到各节点 WinRM 可达 | 各节点到 SMB 共享可达 |
| 额外基础设施 | 无 | 需要 SMB 共享(或类似文件服务) |
| 传输效率 | 逐机重复传输 | 一次上传,N 次拉取 |
| Playbook 复杂度 | 低 | 中(多一个共享目录准备步骤) |
| 覆盖语义 | 同方案二 | 同方案一 |
推荐决策路径 :先数节点。≤10 台且 zip 不大,直接用方案一,简单直接。>10 台或 zip 较大,评估共享目录是否现成可用。如果有现成的部署共享盘(很多企业环境都有),方案二能显著减少部署窗口时间。如果没有,搭建共享目录的运维成本可能抵消收益,此时方案一配合 serial 分批执行仍是务实选择。
作者简介
Moshow,一名热爱技术与分享的 Technical Lead & 数据科学家 & SpringBoot 专家。CSDN 博客之星 2025 年度 TOP100,Github 4K stars。
持有多项权威认证,包括:GCA 谷歌云架构师认证、Neo4j 数据科学家认证、阿里云 ACP 认证、金蝶高级 CRM 供应链工程师认证、PMP 项目管理认证、CSPM 项目管理专业人员能力评价认证、广东省人工智能训练师认证。
