
做自动化运维这行第一课往往不是学Playbook语法而是先把控制的通道打通。Ansible默认走SSH协议去连被管服务器你写一百个任务最后都得落到“这台机器能连上那台机器”这个基础上。SSH免密登录做不扎实ansible命令就会卡在各种密码交互上轻则效率低下重则把整个批量流程直接卡死。这篇文章是Ansible自动化运维系列里关于SSH免密登录控制的部分核心就一件事在Ansible的控制节点与被管节点之间用SSH密钥机制彻底替代密码登录实现免密、批量、可控的服务器访问。如果你是刚开始搭Ansible环境或者已经跑通但偶尔还要输密码、总觉得权限管理有点混乱这篇内容应该能帮你把底层通道彻底理清楚。整篇会从原理讲到实操再讲到问题排查尽量让新手跟得上也能让有基础的人补上几个平时容易忽略的细节。1. 为什么搞Ansible前要先打通SSH免密通道1.1 Ansible的通信机制决定了免密是刚需Ansible和很多自动化工具不一样它不在被管服务器上装Agent。它靠的是控制节点通过SSH协议去连接目标机器把要执行的模块推过去跑完再拿结果回来。这个设计让Ansible很轻量但代价是它对SSH的依赖特别强几乎每一条任务都对应一次SSH连接。这意味着控制节点到每台被管节点的连接链路必须稳定、可复用。如果靠密码交互去连Ansible会卡在“等待输入密码”这一步自动化就变成半自动了。尤其是执行几十台、上百台机器的批量操作时每台机器弹一次密码提示整条流水线就被拖成手工活了。所以把SSH免密登录配好是Ansible落地的前置条件。我见过一些人用ansible_ssh_pass直接在Inventory里写密码还有人用sshpass包一层密码参数短时间内确实能跑起来。但密码本身会过期、会被安全策略禁止远程登录、还会因为特殊字符转义出各种幺蛾子。生产环境最稳的路径就是把密钥认证跑通让SSH连接对调用方完全透明。1.2 密码登录为什么在批量场景下撑不住单个密码登录日常用没什么问题一旦进入自动化运维场景痛点会集中爆发。第一是交互阻塞。SSH密码登录是交互式的它需要从终端读取输入。Ansible在非交互模式下没法直接输入密码虽然可以借助参数绕过但本质上是把密码躺在配置里安全隐患不小。第二是密码本身的生命周期问题。企业中一般有密码策略90天强制改密一次如果自动化链路依赖密码一到改密窗口整个Ansible全部瘫痪这个场面我经历过一次就不想再经历了。第三是审计和管理混乱。谁用了哪个密码去连哪台机器很难追溯权限回收更是麻烦。相比之下密钥认证天然适合批量场景。密钥对是一次生成的私钥保存在控制节点公钥统一分发到所有被管服务器只要私钥不泄露访问权限就始终可控。就算某台被管的公钥需要撤销从authorized_keys里删掉对应条目就行不用所有机器一起改密码。1.3 SSH密钥认证的底层逻辑一把钥匙一把锁SSH密钥认证的原理可以理解成“一把钥匙一把锁”的关系。整个体系里有两个关键文件公钥和私钥。公钥是挂锁分发到被管服务器的~/.ssh/authorized_keys文件里私钥是开锁的钥匙留在控制节点本地。你拿私钥去连服务器时SSH服务端会先用authorized_keys里的公钥生成一个挑战challenge发给你控制节点用私钥对这个挑战做签名响应服务端再用公钥验证这个签名。签名验证通过连接就建立了。整个过程里私钥永远不会离开控制节点更不会在网络里传输。这就是密钥认证比密码认证更利于自动化的原因。服务器判断的是“你是不是持有那把唯一的私钥”而不是“密码字符串对不对”。一旦配置好后续连接完全不需要人工介入这才是自动化工具需要的通道。理解了这一点后面排查问题就知道往哪个方向看要么是私钥对不上要么是公钥没放对位置要么是权限类问题导致SSH服务端不认。2. 实验环境规划与基础检查2.1 服务器清单与角色划分动手之前先规划好环境。以最常见的初始化场景为例控制节点1台安装Ansible保存SSH私钥负责下发任务。被管节点2~3台安装SSH服务端保存控制节点的公钥处于待命接收指令的状态。假设控制节点IP是192.168.1.10被管节点是192.168.1.11、192.168.1.12、192.168.1.13。操作系统建议统一用同一发行版至少SSH版本要接近不然在一个环境踩的坑换到另一个环境可能又多出几个新问题。当然这只是最小实验拓扑生产环境中被管节点可能几十上百台但配置思路是一样的。在实际项目中我更推荐先把主机清单列出来按角色分组哪些是Web节点、哪些是数据库节点、哪些是应用节点。分组本身是后面Inventory设计的一部分提前规划好后面给Ansible配置组变量时就顺手了。2.2 检查SSH服务端状态与必要组件在走密钥分发流程前先确认被管节点SSH服务是正常工作的。用命令行检查systemctl status sshd如果服务没跑起来先启动再设置开机自启systemctl start sshd systemctl enable sshd接着确认sshd_config里的关键配置没有被改坏。默认情况下PubkeyAuthentication是yes但如果之前有人优化过安全策略可能被关掉那后面怎么配都不可能免密登录成功。检查方法grep -E PubkeyAuthentication|PasswordAuthentication /etc/ssh/sshd_config如果你在测试阶段不想折腾太多只要PubkeyAuthentication yes存在就行PasswordAuthentication暂时保持yes因为初次分发公钥还要靠密码登录后面再收紧策略。控制节点那边确认一下Ansible装好没有。装的方法每个发行版不太一样比如CentOS/RHEL和Debian/Ubuntu的包管理器命令不同但核心就一条ansible --version能正常输出版本信息。另外确认控制节点有ssh-keygen和ssh-copy-id命令前者基本是OpenSSH自带的后者可能在部分最小化安装中需要单独装。2.3 密钥类型选型Ed25519还是RSA很多入门教程直接就是ssh-keygen -t rsa一条命令回车到底能用是能用但我觉得值得花两分钟考虑一下密钥类型。现在主流的两个选择是RSA和Ed25519。RSA的优势是兼容性好老版本Linux、老网络设备基本都认。但为了保证安全RSA密钥长度至少建议2048位更多时候推荐3072或4096。密钥文件大生成速度慢验证过程也更费资源。Ed25519是椭圆曲线签名算法OpenSSH 6.5以上就支持了到现在十多年了主流Linux发行版基本都覆盖。它的密钥长度短、生成快、验证性能好安全性上也被广泛认可。我的建议是如果被管节点都是近些年安装的系统优先选Ed25519如果环境里还有很老的操作系统或网络设备那就退回到RSA 4096。这里给出一个对比表格方便按实际情况选对比项Ed25519RSA2048密钥长度固定较短可选越长越安全认证速度快较长密钥下稍慢兼容性OpenSSH 6.5几乎所有平台适用场景现代Linux/Unix老系统、网络设备推荐级别优先兼容性兜底生产环境中如果统一用新系统我就推荐Ed25519。一个典型的场景是给几十台新装好的CentOS 9或Ubuntu 22.04服务器做初始化用Ed25519整体体验很顺。如果混入一台2012年的老服务器那台单独补一个RSA密钥也就完事了。3. SSH密钥的生成与批量分发核心实操3.1 用ssh-keygen生成密钥对含参数逐项解析在控制节点上以想要执行Ansible的用户登录比如专门建一个ansible用户或者直接用root取决于你的风险偏好执行ssh-keygen -t ed25519 -C ansible-control-node -f ~/.ssh/ansible_ed25519 -N 逐项拆解一下这几个参数因为很多教程直接跳过解释导致后面出了问题不知道从哪里看-t ed25519指定密钥类型前面已经聊过选型逻辑这里用的是Ed25519。-C ansible-control-node给密钥加注释。这个注释最后会出现在公钥文件的末尾相当于给这把钥匙做个标识。运维环境里如果有多套密钥体系注释写清楚是哪个控制节点、什么用途能省去很多排查时间。-f ~/.ssh/ansible_ed25519指定私钥保存的路径和文件名。拆开写的好处是让密钥名一眼就能认出用途方便管理。-N 设置私钥的passphrase也就是私钥口令。这里先留空是为了让Ansible调用SSH时不需要再额外输入口令保证自动化链路全自动。生成后会在~/.ssh目录下出现两个文件ls -l ~/.ssh/ansible_ed25519*ansible_ed25519是私钥ansible_ed25519.pub是公钥。此时可以顺便看一下公钥内容cat ~/.ssh/ansible_ed25519.pub输出大概是ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxxxx ansible-control-node后缀那段ansible-control-node就是我们用-C加的注释。后面分发到被管服务器后可以通过这个注释快速识别是来自哪个控制节点的公钥。这里有一个很重要的权限点私钥文件的权限必须是600即只允许当前用户读写。如果权限太宽松SSH客户端会直接拒绝使用这个私钥。生成后可以检查一下ls -l ~/.ssh/ansible_ed25519正常输出应该是-rw-------。如果不是手动修正chmod 600 ~/.ssh/ansible_ed25519注意~/.ssh目录本身的权限也建议设置为700。这个细节很容易被忽略但SSH对目录权限很敏感目录权限过宽照样会报错。3.2 首次分发ssh-copy-id的正确姿势密钥对生成后下一步是把公钥放到被管节点的authorized_keys里。最推荐的方式是使用ssh-copy-id命令它会自动处理文件是否存在、内容是否重复、权限是否正确这些细节。以192.168.1.11为例ssh-copy-id -i ~/.ssh/ansible_ed25519.pub -p 22 root192.168.1.11命令执行后SSH会提示输入目标机器的root密码。输入正确密码后公钥会被追加到目标机器的~/.ssh/authorized_keys文件中。此时可以顺手验证一下免密登录是否生效ssh -i ~/.ssh/ansible_ed25519 -p 22 root192.168.1.11如果能直接进入目标机器的shell而不需要输入密码说明密钥认证已经成功了。退出后对192.168.1.12和192.168.1.13重复同样的操作。ssh-copy-id内部做了三件事检查本地公钥文件是否存在检查目标机器authorized_keys文件是否存在把公钥内容追加进去并设置正确权限。这比手动用管道执行cat id_ed25519.pub authorized_keys要稳得多因为手动追加容易踩到权限和路径不一致的坑。3.3 批量分发公钥的实战脚本被管节点只有两三个时手工一条条执行没问题。一旦数量上来手工操作就效率太低了。这里给一个批量分发的脚本思路#!/bin/bash NODES192.168.1.11 192.168.1.12 192.168.1.13 SSH_PASSWORDYourPassword for NODE in $NODES; do sshpass -p $SSH_PASSWORD ssh-copy-id -i ~/.ssh/ansible_ed25519.pub \ -o StrictHostKeyCheckingno -p 22 root$NODE done这段脚本用到了sshpass它允许在命令行直接指定密码让首次复制公钥的过程不需要人工干预。但这里有两个点必须提醒第一sshpass是明文把密码暴露在命令行里的在测试环境图省事可以在正式环境这么做等于把密码拱手送人。生产环境我一般建议只在初始化的内网环境用用完立刻改密码或者改用更安全的凭据管理平台来配合分发。第二-o StrictHostKeyCheckingno是为了跳过首次连接时的主机指纹确认。如果你不想降低安全级别可以先用ssh-keyscan把所有被管节点的主机指纹预存到控制节点的known_hosts里这样既不用交互确认也没有完全关闭指纹校验ssh-keyscan -p 22 192.168.1.11 192.168.1.12 192.168.1.13 ~/.ssh/known_hosts再强调一次批量分发公钥只是初始化过程前期的目标是快速打通通道。通道一旦打通后续运维应当切换到纯密钥认证模式密码登录该关就关。4. 接入AnsibleInventory与连通性验证4.1 最小可用的Inventory配置公钥分发完成、免密登录验证通过后Ansible部分就可以开始配置了。第一步是写Inventory也就是告诉Ansible要管理哪些机器。创建一个最小化的Inventory文件hosts.ini[web] 192.168.1.11 ansible_userroot 192.168.1.12 ansible_userroot [db] 192.168.1.13 ansible_userroot这里分成了web和db两个组组名可以按你的业务架构自定义后面Playbook可以按组去批量执行任务。ansible_userroot指定登录用户如果前面分发公钥时用的是root这里就是这个如果用的是普通用户加sudo后面还需要配置become等参数但那个属于另一个话题了。如果被管节点的SSH端口不是默认的22可以在主机后面补上端口参数[web] 192.168.1.11 ansible_userroot ansible_port22224.2 ansible.cfg关键参数说明Inventory写好后最好在控制节点写一个ansible.cfg把常用的连接参数固定下来不然每次敲ansible命令都要在命令行加一堆参数。[defaults] inventory ./hosts.ini host_key_checking False private_key_file ~/.ssh/ansible_ed25519 remote_user root [ssh_connection] ssh_args -o ControlMasterauto -o ControlPersist60s几个参数说明一下inventory指定Inventory文件路径可以是相对路径或绝对路径。这样ansible all -m ping就不用每次加-i。host_key_checking False关闭首次连接时的主机指纹交互确认避免首次连接卡在“yes/no”提示上。生产环境如果条件允许更稳妥的做法是用ssh-keyscan预存指纹但至少在Ansible内网环境中关闭这个检查是普遍操作。private_key_file指定Ansible用来连接被管节点的私钥文件路径。这里必须跟前面生成的私钥路径一致。ssh_args开启SSH连接复用。ControlMasterauto和ControlPersist60s的意思是建立一个连接后在60秒内复用同一个SSH连接做批量操作时能明显减少握手耗时大并发任务尤其有效。4.3 用ping模块验证全链路连通配置写完后执行ansible all -m ping如果一切正常输出会是类似这样的192.168.1.11 | SUCCESS { changed: false, ping: pong } 192.168.1.12 | SUCCESS { changed: false, ping: pong } 192.168.1.13 | SUCCESS { changed: false, ping: pong }看到三台机器都返回pong说明从控制节点到被管节点的SSH密钥通道已经打通Ansible的Inventory配置也没问题。到这一步整个免密登录控制的基础工作就算完成了。如果某台机器报错可以把报错信息对着下一节的问题排查表过一遍。ping模块能做到的是最底层的连通性验证这个通了后面写Playbook就不会在连接层面浪费时间了。5. 高频问题排查与避坑实录5.1 认证失败的定位思路最常见的问题就是Permission denied (publickey)。这个报错看起来吓人但本质上就那几类原因公钥没有正确追加到目标机器的authorized_keys。解决方法是登录到被管节点确认~/.ssh/authorized_keys里确实有控制节点的公钥内容。authorized_keys文件权限不正确。SSH对权限很敏感文件权限不能是其他用户可写一般建议600。如果发现权限过宽马上chmod 600 ~/.ssh/authorized_keys。私钥权限不正确。这个前面提过私钥文件要600权限过宽SSH会拒绝使用。可以执行chmod 600 ~/.ssh/ansible_ed25519修复。sshd_config中PubkeyAuthentication被设为no。用root登录被管节点检查配置并改为yes后重启sshd。SELinux导致SSH读取authorized_keys受限。这种情况在CentOS/RHEL系比较常见可以检查SELinux的提示或者临时用restorecon -R -v ~/.ssh恢复上下文。排查时我一般会先跑一个带详细日志的命令ssh -i ~/.ssh/ansible_ed25519 -vvv -p 22 root192.168.1.11-vvv会把SSH客户端的调试信息全部打出来看它卡在哪一步。如果看到Offering public key后面跟Authentications that can continue: publickey那大概率是公钥认证没通过如果看到Connection closed by ...可能是sshd本身拒绝了连接。日志能帮你快速缩小范围省得瞎猜。5.2 known_hosts与首次连接确认问题我第一次做批量分发的时候遇到过不少Host key verification failed的报错。这个报错的原因是控制节点的known_hosts文件里已经有了目标IP的记录但目标机器的SSH主机指纹变了或者压根没有这条记录导致的交互确认被自动化环境卡住了。在Ansible场景下这个报错很烦人因为它会中断整个批量任务。解决方法有三个一是临时关闭指纹检查在ansible.cfg里设置host_key_checking False。这是最简单的方式。二是用ssh-keygen -R删掉旧指纹。如果目标机器的指纹变了可以执行ssh-keygen -R 192.168.1.11然后重新连接让它把新指纹写入known_hosts。三是用ssh-keyscan提前预存所有目标机器的指纹ssh-keyscan -p 22 192.168.1.11 192.168.1.12 192.168.1.13 ~/.ssh/known_hosts三种方式按需选择。测试环境用第一种最省事生产环境如果安全要求严格用第三种更合理。5.3 非22端口、多用户场景的处理SSH默认端口是22但有些企业的安全策略会要求改成其他端口。如果被管节点SSH端口不是22分发和连接都有对应调整。分发公钥时ssh-copy-id -i ~/.ssh/ansible_ed25519.pub -p 2222 root192.168.1.11Ansible连接时Inventory里加端口参数或者命令行指定ansible all -m ping -e ansible_port2222多用户场景下公钥要放到对应用户的~/.ssh/authorized_keys里。比如你想用devops用户跑Ansible那就把公钥放到/home/devops/.ssh/authorized_keys而不是放root的。Inventory里也相应改[web] 192.168.1.11 ansible_userdevops如果用普通用户而任务需要root权限那就涉及Ansible的become机制。这里提醒一句公钥分发的用户跟Ansible执行任务的用户必须一致否则虽然密钥认证成功了但换一个用户还是提示权限不足我第一次在这个问题上绕了蛮久。5.4 批量分发时的密码交互处理与安全取舍批量分发公钥时sshpass是一个很方便但也很敏感的工具。它的本质是把密码以明文形式传给SSH客户端这在命令行里、脚本历史记录里、ps的输出里都可能暴露密码。如果你想用至少要注意这几点不要在多人共享的机器上执行带明文密码的批量分发脚本。执行完马上把脚本里的密码占位符改掉或删除。优先考虑从已经免密的机器再往其他机器分发公钥避免大面积使用密码。举个例子如果有一台192.168.1.10已经免密登录了192.168.1.11而192.168.1.11又能免密登录192.168.1.12那你可以通过链式分发避免集中暴露密码。这个方式在安全要求较高的场景下更合适但配置复杂度也更高。6. 密钥的日常管理与安全加固建议6.1 密钥权限、存放与备份规范密钥认证打通后最怕的事情就是私钥丢失或泄露。私钥一旦泄露所有配了对应公钥的服务器都等于被人拿走了钥匙。所以日常管理有几个习惯必须养成。第一私钥存放位置固定权限必须是600。不要把私钥放到一个所有用户都能读的目录里比如/tmp或者项目的公共目录。第二建议给私钥设置passphrase。前面我为了Ansible全自动运行把passphrase留空了。但在生产环境尤其是私钥需要拷贝到多人协作的开发机时passphrase是最后一道防线。留空的风险是任何能读到私钥文件的人都能直接使用它。设置了passphrase之后即使私钥文件被复制出去对方也没有口令可用。当然如果设置了passphraseAnsible那边就得配合ssh-agent来缓存口令不然还是会卡交互。这个取舍要结合团队的安全策略来定。第三私钥要做好备份。备份文件建议加密存储比如放到公司的密钥管理平台或者用加密压缩包存到离线位置。别到时候一台控制节点硬盘坏了整套自动化通道就得全部重建。还有一个细节公钥虽然不带敏感信息但也不是完全无所谓。公钥注释字段里如果有明确的机器名、人员名别人扫一眼就能知道哪台机器是哪个控制节点的有信息暴露风险。所以注释写得规范但不啰嗦就够了。6.2 服务端加固禁密码、限来源密钥认证全部验证通过之后就可以考虑把被管节点的密码登录关掉了。这个动作在sshd_config里完成vim /etc/ssh/sshd_config把以下几项配置好PasswordAuthentication no PubkeyAuthentication yes PermitRootLogin prohibit-passwordPasswordAuthentication no关闭密码认证只允许密钥认证。PubkeyAuthentication yes确保公钥认证是开启的。PermitRootLogin prohibit-password允许root通过密钥登录但禁止直接密码登录。如果你的安全策略不允许root远程登录可以改成no但那样的话Ansible需要在Inventory里配一个普通用户加become处理方式会不太一样。修改后重启sshdsystemctl restart sshd警告在关闭密码登录前务必先在另一个终端里确认密钥登录是正常的。我身边就有人把PasswordAuthentication改成no之后一不留神把当前连接断开结果新连接又因为密钥配置有问题死活进不去最后只能跑去机房或者通过带外管理口救机。这个坑一旦踩进去代价相当大。如果被管节点有防火墙可以考虑把SSH端口限制为只允许控制节点IP访问。例如用firewalldfirewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.1.10 port port22 protocoltcp accept firewall-cmd --reload这样即使其他机器拿到了私钥也没法从任意来源发起连接。6.3 密钥轮换与账号离职回收长期不换密钥就跟长期不换密码一样风险会随时间累积。密钥轮换一般分两种节奏一种是定期轮换比如每半年或一年给控制节点生成一套新密钥对同时把旧公钥从所有被管节点移除。这个动作虽然简单但量大的时候需要自动化辅助。你可以写一个临时Playbook先分发新公钥验证新连接正常后再清理authorized_keys里的旧公钥条目。另一种是事件驱动轮换。比如发现私钥可能泄露、某个运维人员离职、或者某台跳板机被入侵这时候必须立刻换。换的流程和上面一样但动作要快不能拖到第二天。账号离职回收也依赖密钥管理。如果用的是个人私钥离职时要确保他的公钥从所有机器的authorized_keys里删除。这个操作可以用Ansible批量执行但前提是你自己还有一套管理通道。更规范的做法是生产环境中用跳板机统一管理访问控制节点本身不直接暴露公网这样密钥的回收半径就小很多。我在实际项目里一般会维护一个“密钥登记表”记录每套密钥对应的控制节点、用途、负责人、创建时间、轮换时间。这个表看起来原始但排障和审计的时候非常有用。尤其是多套密钥混用的时候没有登记表很容易把A项目的公钥分发到B项目的机器上后面排查认证问题就全凭猜了。最后再分享一个小技巧。SSH密钥免密登录配置好之后你可以在Ansible的每个Playbook开头加一个ansible all -m ping的验证步骤或者直接在跑任务前先快速ping一圈。这样一旦有节点因为网络变动、密钥被误删等原因掉线你能在执行正式任务前就发现问题而不是等Playbook跑到一半才爆一堆连接超时。这个习惯帮我提前拦截过不少问题强烈建议保留。