用 Ansible 从 Linux 管理 Windows 服务器:WinRM 机制与认证取舍

管理多台 Windows 服务器时,通过远程桌面逐台登录执行重复配置,低效到让人难以忍受。Linux 那边有 SSH 配合脚本就能批量操作,Windows 长期以来缺少顺手的方案。Ansible 的 Windows 支持通过 WinRM 协议打通了这条路,控制机留在 Linux,被管机器不需要装 agent。

WinRM 是什么,为什么不用 SSH

Windows 没有 SSH(直到 Server 2019 才原生支持 OpenSSH),但自带了一套远程管理协议 WinRM(Windows Remote Management)。它是 WS-Management 标准的微软实现,基于 SOAP over HTTP,默认监听 5985(HTTP)和 5986(HTTPS)两个端口。Ansible 对 Windows 的支持就建立在 WinRM 之上。

和 Linux 那边 Ansible 通过 SSH 调 Python 模块不同,Windows 被管节点上不需要装 Ansible 客户端,也不需要 Python。Ansible 把模块代码通过 WinRM 推过去执行,依赖的是 Windows 内置的 PowerShell 远程能力。这是这套方案最大的好处:被管机器零侵入,WinRM 服务在 Vista / Server 2008 之后默认随系统存在,只需启用。

代价是协议层面比 SSH 重。WinRM 是 SOAP,每条消息都是 XML,传输效率和 SSH 比有明显差距,大批量文件传输或高频模块调用时会感觉出来。日常配置变更这种低频场景完全够用。

认证方式与传输层的选择

WinRM 的安全由两个独立维度决定:认证方式和传输层。两者可以组合,但有约束。

认证方式有几种,各有取舍:

  • Basic:用户名密码,凭据以明文形式走 HTTP(需要服务端开启 AllowUnencrypted)。最简单,也最不安全,只适合内网测试。
  • NTLM:Windows 原生挑战响应,不需要域,比 Basic 安全一些,但跨网段和某些受限场景下表现不稳定。
  • Kerberos:域环境下的首选,需要控制机装 python-kerberos 并配置 krb5.conf,拿到票据后能单点登录多台机器。配置成本最高,体验也最好。
  • CredSSP:凭据委派,用于需要二次跳转(从被管机再连第三台机器)的场景,配置较繁琐。
  • Certificate:用客户端证书替代密码,适合做无密码化自动化,但证书签发和管理本身有成本。

传输层是 HTTP(5985,明文)或 HTTPS(5986,加密)。Basic 走 HTTP 必须显式开 AllowUnencrypted,否则服务端拒绝;走 HTTPS 则不需要。NTLM、Kerberos 自带加密协商,可以走 HTTP,但仍然建议外面套一层 HTTPS。

本文用 Basic + HTTP,是最简组合,也是最不安全的一个。选它的原因很简单:内网测试环境,机器少,先把链路打通。生产环境应该走 5986 + Kerberos 或 Certificate,不要在公网或半可信网络上跑 Basic。

控制节点配置(Linux 侧)

安装 Ansible

Ubuntu 上直接装:

1
sudo apt-get update && sudo apt-get install ansible

Ubuntu 仓库里的版本可能偏旧,需要新版本用 pip:

1
pip3 install ansible

主机清单

创建并编辑 /etc/ansible/hosts

1
sudo touch /etc/ansible/hosts
1
2
3
4
5
6
7
8
[windows_servers]
win-host1 ansible_host=192.168.31.4

[windows_servers:vars]
ansible_connection=winrm
ansible_winrm_transport=basic
ansible_winrm_server_cert_validation=ignore
ansible_port=5985

几个关键参数:

  • ansible_connection=winrm:指定用 WinRM 而不是 SSH。
  • ansible_winrm_transport=basic:认证方式选 Basic。
  • ansible_winrm_server_cert_validation=ignore:忽略服务端证书校验。Basic over HTTP 本来就没证书,这个参数在切到 HTTPS 时才真正有意义,这里留着是为了将来切换时不踩坑。
  • ansible_port=5985:WinRM 的 HTTP 端口。走 HTTPS 改成 5986。

凭据文件

把密码单独放 host_vars,避免明文写在清单里:

1
sudo touch /etc/ansible/host_vars/win-host1.yml
1
2
ansible_user: test
ansible_password: "test123."

密码里有特殊字符(比如这里的 .)时,务必用引号包裹,否则 YAML 解析可能出问题。生产环境应该用 Ansible Vault 加密这个文件,明文只是演示。

配置完目录长这样:

1
2
3
4
/etc/ansible
├── hosts
└── host_vars
└── win-host1.yml

被管节点配置(Windows 侧)

网络类型必须是”专用”

WinRM 服务对网络配置文件有要求,网络类型必须是”专用”或”域”,不能是”公用”。公用网络配置下 Windows 会拒绝接受 WinRM 连接,这是最常见的连不上的原因之一。

路径:设置 -> 网络和 Internet -> 以太网 -> 当前连接 -> 改为”专用网络”。

配置专用网络

创建专用用户组

给 Ansible 单独建个组,方便后续按组授权,不要直接拿管理员账号跑:

  1. 计算机管理 -> 本地用户和组 -> 组
  2. 新建组 ansible
  3. 把要远程管理的用户加进去

创建 ansible 用户组

启用 WinRM

Ansible 官方在 GitHub 提供了一个 ConfigureRemotingForAnsible.ps1 脚本可以一键配好。但它会改不少东西,我倾向于手动执行关键命令,至少知道自己改了什么。

以管理员权限打开 PowerShell,依次执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 启用 WinRM 服务并做基础配置
winrm quickconfig -q

# 开启 Basic 认证和明文传输(仅内网环境)
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'

# 防火墙放行 5985
New-NetFirewallRule -Name "WinRM" -DisplayName "WinRM" -Protocol TCP -LocalPort 5985 -Action Allow

# 把 ansible 组加入 Remote Management Users
net localgroup "Remote Management Users" ansible /add

# 验证监听器
winrm e winrm/config/listener

AllowUnencrypted="true" 这一项很多人会漏。Basic 认证默认要求加密通道(HTTPS),走 HTTP 明文就必须显式打开这个开关,否则 Ansible 连接时会报 401 或加密相关错误。

命令执行结果参考:

启用 WinRM

配置认证方式

配置防火墙

授权用户组

验证监听器

看到监听器跑在 5985 端口,WinRM 就绪了。

连接测试

回到 Linux 控制节点:

1
ansible windows_servers -m win_ping

成功的话返回:

1
2
3
4
win-host1 | SUCCESS => {
"changed": false,
"ping": "pong"
}

连接测试成功

win_ping 不是 ICMP ping,它走的是完整的 WinRM 认证和传输链路,返回 pong 说明整条路径都通。

加更多机器

首台通了之后,扩容就是重复劳动。

主机清单加几行:

1
2
3
4
5
6
7
8
9
10
11
12
[windows_servers]
win-host1 ansible_host=192.168.31.4
win-host2 ansible_host=192.168.31.5
win-host3 ansible_host=192.168.31.6
win-host4 ansible_host=192.168.31.7
win-host5 ansible_host=192.168.31.8

[windows_servers:vars]
ansible_connection=winrm
ansible_winrm_transport=basic
ansible_winrm_server_cert_validation=ignore
ansible_port=5985

每台机器在 host_vars/ 下建一个凭据文件:

1
2
3
4
sudo touch /etc/ansible/host_vars/win-host2.yml
sudo touch /etc/ansible/host_vars/win-host3.yml
sudo touch /etc/ansible/host_vars/win-host4.yml
sudo touch /etc/ansible/host_vars/win-host5.yml
1
2
3
# win-host2.yml
ansible_user: admin
ansible_password: "password123"

每台 Windows 机器上把”网络类型改专用、建 ansible 组、跑那段 PowerShell”重复一遍。批量验证:

1
ansible windows_servers -m win_ping
1
2
3
4
5
win-host1 | SUCCESS => { "changed": false, "ping": "pong" }
win-host2 | SUCCESS => { "changed": false, "ping": "pong" }
win-host3 | SUCCESS => { "changed": false, "ping": "pong" }
win-host4 | SUCCESS => { "changed": false, "ping": "pong" }
win-host5 | SUCCESS => { "changed": false, "ping": "pong" }

到这步就能用 Ansible 统一管理所有 Windows 服务器了。

踩过的坑

Windows LTSC / Home 版本不可用。 LTSC 功能精简过度,部分 Ansible 模块跑不起来;Home 版本干脆没有 WinRM 服务。要用 Server 版,或者专业版 / 企业版。这是选机器时就得确认的前提,不是配置能解决的。

win_ping 连接超时。 优先排查三件事:Linux 能不能 ping 通 Windows;5985 端口防火墙有没有放行(本地防火墙和网络设备都要看);WinRM 服务和监听器是否正常,在 Windows 上跑 winrm e winrm/config/listener 确认。

认证失败 401。 多半是用户没加进 Remote Management Users 组,或者 Basic 认证没开。验证方式:winrm get winrm/config/service/auth,看 Basic = true。密码里有特殊字符一定要加引号。

网络类型不匹配。 公用网络下 WinRM 直接拒绝连接,改回专用或域。

关于安全的实话

这套配置是 Basic + HTTP + 明文,凭据在网络上是可以被抓到的。我用在完全隔离的内网测试环境,机器之间可信。环境如果不是这样,不要照搬:

  • 公网或半可信网络,必须走 5986(HTTPS),用正式证书或者自签证书配合 ansible_winrm_server_cert_validation=ignore
  • 域环境优先上 Kerberos,一次票据拿到多台机器,体验比 Basic 好太多,也不用在每台机器上单独配密码。
  • 凭据文件用 Ansible Vault 加密,别明文躺着。
  • 定期轮换密码,哪怕在内网。

把链路打通是第一步,把链路做安全是第二步,两步不能省第二步。