
做运维这一行只要碰过批量服务器管理迟早都会遇到同一个问题几十台甚至上百台机器难道每次都要手动输密码登录如果只是登录还好说等你要在每台机器上执行命令、传文件、跑脚本的时候密码输入会直接把效率拖到零。这也是Ansible这类自动化运维工具存在的价值——但它想真正跑起来第一步必须先解决服务器间的免密登录。说白了就是用SSH密钥把“手动输密码”这个动作干掉让控制端可以自动、安全、批量地访问所有被管节点。这篇内容主要讲两件事第一为什么要用密钥而不是密码第二从生成密钥、批量分发、到验证效果完整走通一遍SSH密钥免密登录的流程。适合刚接触Ansible、还没搞定控制端和被控端连接关系的同学也适合已经在用Ansible但登录方式一直不规范的运维老手做个自查。整个过程不需要搞什么高深的技术一个能上网的Linux环境就能全部跑通。1. 整体思路拆解为什么要用SSH密钥而不是密码1.1 密码登录在自动化场景下的先天缺陷很多人一开始觉得Ansible不就是SSH连过去执行命令吗那用密码登录不就行了吗我在早期也这么干过但用过一次批量场景就不会再想用第二次。先说第一个问题密码本身很难在脚本里安全保存。你可以在ansible的hosts文件里用ansible_ssh_pass指定密码然后写进playbook但这样做等于把服务器的密码明文写到了一个文件里。但凡这台管理机被攻破或者文件被同事误传出去你所有服务器的密码就全泄露了。更别提代码仓库里一旦不小心提交了这个文件历史记录里都会留下痕迹清理起来非常麻烦。第二个问题是效率。SSH密码认证在每次连接时都要经过一次加密握手和密码校验这个过程比密钥认证慢不少。在几十台机器的批量任务中这个差异还会被放大。你可能觉得每台机器也就差个几百毫秒但批量任务需要同时连几十台机器叠加起来延迟就非常明显了。第三个问题是交互性。Ansible的ad-hoc命令和playbook都是非交互式的密码认证本身就需要交互输入虽然可以用sshpass之类的工具绕过但这也引入了额外的依赖而且在不同Linux发行版上安装方式还不一样等于给自己埋了一个新的兼容性坑。1.2 密钥认证机制到底是怎么工作的SSH密钥认证的核心是“一对钥匙”私钥留在本地控制端公钥放到目标服务器的authorized_keys文件里。连接时服务器会生成一个随机挑战用你放上去的公钥加密后发给客户端客户端用本地私钥解密并返回结果服务器验证通过后即完成身份认证。这个过程不需要在网络上传送任何形式的密码安全性上比密码高一个量级而且完全不依赖交互输入。更关键的是它可以配合ssh-agent把私钥的密码缓存住真正做到一次解锁、全程免密。用生活化的比喻来讲密码登录就像你每次进小区都要报门牌号和姓名保安核实了才放你进去。密钥登录则像你手里有一把特制的钥匙和一张门禁卡门禁系统一刷就知道你是业主根本不用废话。自动化和批量操作需要的就是这种“刷一下就放行”的体验。1.3 Ansible与SSH免密的依赖关系Ansible的架构很简单一个控制端通常是你自己的电脑或一台跳板机若干个被控节点。控制端通过SSH协议连接被控节点并执行任务不需要在被控节点上安装任何agent。这意味着SSH连接的质量直接决定了Ansible运行的稳定性和效率。如果SSH连不上Ansible报的错五花八门——有的是连接超时有的是认证失败有的是host key校验不通过。你本来是想跑个部署任务结果先花半小时排查网络和认证问题心态直接爆炸。所以我的建议是在正式使用Ansible之前先把SSH免密这一步彻底搞定。这不仅是技术需要也是运维工作流的基本卫生习惯。2. 部署前的环境准备控制端与被控端的规划思路2.1 控制端和被控节点的基本要求先明确一下角色划分。控制端是你执行ansible命令的机器被控节点是你要管理的目标机器。两者不需要是同一个操作系统也不需要相同的发行版只要都支持SSH协议即可。我在实际工作中就遇到过控制端是Ubuntu被控节点是CentOS、Debian、甚至OpenEuler混在一起的情况SSH密钥机制完全不受影响。Ansible控制端要求是Linux或者macOSWindows的话需要借助WSL来完成。被控节点只需要有Python和SSH服务就行版本倒不用太纠结。另外要注意一个容易忽略的点虽然Ansible 2.9以上版本在被控节点没有Python 2.7也能够通过一些workaround运行但如果你被控节点的Python版本太老后期执行很多模块时还是会遇到兼容性问题。建议在正式使用前确认一下被控节点的Python版本至少是Python 3.5以上会省心很多。2.2 确认基础工具与SSH服务的状态在我正式开始配置之前都会先做一轮基础检查防止配置过程中突然发现某个基础组件缺失。控制端需要确认的东西包括ssh客户端、ssh-keygen、ssh-copy-id这几个工具是否存在。检查方法很简单which ssh which ssh-keygen which ssh-copy-id如果发现ssh-copy-id不存在可以直接用下面的方式安装# Ubuntu/Debian apt install openssh-client # CentOS/RHEL yum install openssh-clients然后确认被控节点的SSH服务是否正常运行systemctl status sshd如果服务没跑起来先启动它systemctl start sshd systemctl enable sshd被控节点还需要确认SSH的端口是22还是自定义的。如果是自定义端口后面连接时就要用-p参数指定这个在后面生成配置时要特别注意。2.3 一个容易被忽略的问题SSH服务端配置优化每次在新环境上配SSH我都会顺手检查一下被控节点的/etc/ssh/sshd_config重点关注几个和免密登录相关的参数。第一个是PubkeyAuthentication默认是yes但有些安全策略比较严格的系统可能改了它导致公钥认证一直失败但你又找不到原因。第二个是AuthorizedKeysFile默认指向.ssh/authorized_keys这个一般是标准配置不用动。第三个是PasswordAuthentication默认情况下是yes但我们配置完成免密登录后可以考虑把它改为no强制所有登录走密钥认证。这样做的好处是杜绝暴力破解密码的可能坏处是你如果密钥配置出错或者私钥丢了就彻底无法登录了。所以我的习惯是先确保密钥登录完全正常再改这个参数并且在改之前一定要用另一个终端保持一个已登录的会话防止改完连接断开又进不去。3. 核心实操过程生成密钥到免密登录的全流程3.1 生成密钥对算法和参数的选型密钥对是免密登录的基础生成它用的是ssh-keygen命令。我在生产环境中推荐使用RSA 4096位或者ED25519算法。RSA的兼容性最好几乎所有旧版本SSH都支持ED25519的密钥更短、性能更好、安全性更高但从我实际接触的环境来看个别很老的Linux发行版对ED25519的支持不够好。如果你管理的机器都比较新直接用ED25519就行如果你跟我一样要兼容一堆老系统那就老老实实用RSA。生成ED25519密钥的命令如下ssh-keygen -t ed25519 -C ansible-control -f ~/.ssh/id_ed25519如果选择RSA命令是ssh-keygen -t rsa -b 4096 -C ansible-control -f ~/.ssh/id_rsa-C参数是注释通常填你的用途或者姓名方便后续辨认哪个密钥是干什么的。-f参数指定生成的文件路径如果不填默认会生成id_ed25519或者id_rsa。执行过程中会提示你是否设置私钥密码passphrase我的建议是在自动化和Ansible场景下不设密码或者配合ssh-agent使用。为什么因为Ansible每次连接时如果私钥有密码就需要交互输入这又回到了自动化最忌讳的瓶颈上。如果你担心私钥泄露应该做的是控制控制端的访问权限而不是给私钥加密码。当然在少数高安全要求的场景下可以给私钥设置密码然后通过ssh-agent缓存它这个后面会讲。3.2 把公钥分发到被控节点单机方法生成密钥对后你会在~/.ssh/目录下看到两个文件私钥id_ed25519或id_rsa和公钥id_ed25519.pub或id_rsa.pub。私钥绝对不能外传公钥可以随便分发。单机分发最简单的方式是ssh-copy-idssh-copy-id -i ~/.ssh/id_ed25519.pub usertarget-server这个命令会自动做三件事连接到目标服务器、提示你输入密码、把公钥追加到目标用户的~/.ssh/authorized_keys文件中。它还会自动创建需要的目录并设置好权限可以说完美解决了新手最容易犯的权限错误。如果你用的不是默认端口就要加-P参数注意是大写P和ssh的-p参数逻辑一样但是在ssh-copy-id里是大写的ssh-copy-id -i ~/.ssh/id_ed25519.pub -P 2222 usertarget-server分发完成后如果一切正常你再连接这台机器就不需要输入密码了ssh usertarget-server如果连接时还要求密码并且你确认公钥已经放上去了那几乎一定是权限问题这个后面排查部分细讲。3.3 批量分发公钥几十台机器怎么办单机分发容易但Ansible要管几十台、上百台机器一台一台ssh-copy-id能把你累死。更合理的思路是写一个批量分发脚本。我在实践中采用的是“手动输入密码只做一次、后续全部自动化”的方式。思路是这样的先把所有节点的IP或者主机名写到一个文件里然后写一个for循环在循环里调用ssh-copy-id每台机器第一次分发时需要手动输入一次密码后续就完全免密了。while read host; do echo 正在处理 $host ... ssh-copy-id -i ~/.ssh/id_ed25519.pub -o StrictHostKeyCheckingaccept-new root$host done hosts.txthosts.txt的内容就是每行一个IP或者主机名192.168.1.101 192.168.1.102 192.168.1.103这个脚本第一次跑的时候你还是要一台台输密码但这已经是最后一次了。跑完之后你后续的Ansible操作就完全不需要密码了。如果你想做到“一条命令跑完所有机器全程不需要手动输密码”那就需要在脚本里处理密码的自动化输入。我个人不推荐在生产环境用sshpass这种方式因为密码会出现在命令行历史里有安全隐患。但如果你真的需要可以用sshpass包一下ssh-copy-id注意用完清理历史记录。另外还有一个更优雅的思路先在一台机器上配置好免密登录然后用Ansible本身来对其他机器做同样的事。但问题是你连第一台机器的免密都没搞定Ansible根本跑不起来。所以这个思路是等基础免密搭好之后再通过Ansible把公钥统一分发到所有新加入的节点这个后面可以单独写一篇先不展开。3.4 批量分发时可以顺手处理的known_hosts很多人在批量分发时会踩到一个坑第一台机器连接时会弹出确认host key的交互提示导致脚本卡住。这是因为SSH客户端默认会询问你是否信任目标主机的host key。解决办法是加一个StrictHostKeyCheckingaccept-new参数自动接受新的host key但不会修改已有条目while read host; do echo 正在处理 $host ... ssh-copy-id -i ~/.ssh/id_ed25519.pub -o StrictHostKeyCheckingaccept-new root$host done hosts.txt如果机器特别多还可以加上GSSAPIAuthenticationno和ConnectTimeout10这样的参数避免某些机器网络不通时卡住很久才报错。3.5 验证免密登录是否真正生效分发结束后别忘了验证。你要确认的不只是“能连上”还要确认Ansible用的那个用户能免密连接。这看起来像废话但我确实遇到过有人把公钥分发到了root的authorized_keys里结果Ansible执行时默认用普通用户连接怎么连都失败。验证方法很简单ssh -o BatchModeyes root192.168.1.101 echo 免密登录成功BatchModeyes的意思是禁止交互输入密码如果认证不通过就直接报错而不是傻傻地等待输入。这个参数在排查免密登录问题时非常好用建议记住。如果要验证一批机器可以写一个循环while read host; do result$(ssh -o BatchModeyes -o ConnectTimeout5 root$host hostname 21) if [[ $? -eq 0 ]]; then echo $host 连接成功: $result else echo $host 连接失败: $result fi done hosts.txt通过这个脚本你可以一眼看出哪些机器的免密配置成功了哪些还没搞定。4. 自动化工具链上的进阶配置4.1 Ansible inventory与ansible.cfg的免密关联SSH免密配置完成只是第一步。真正要让Ansible顺畅地用上这套免密机制还需要在Ansible的配置层面做两件事配置inventory和ansible.cfg。Inverntory是Ansible管理主机的清单文件默认在/etc/ansible/hosts也可以自定义路径。最简单的格式是分组加主机名列表[web] 192.168.1.101 192.168.1.102 [db] 192.168.1.201当免密登录已经配置好之后这个文件里的主机不需要写任何密码相关的内容直接写IP或者主机名就行Ansible会自动用当前用户的SSH私钥去尝试连接。ansible.cfg是Ansible的配置文件有几个和SSH相关的参数建议根据实际情况调整。第一个是私钥文件的路径如果你不只有一个私钥最好明确指定[defaults] inventory ./hosts private_key_file ~/.ssh/id_ed25519 remote_user root host_key_checking False其中host_key_checking设置为False是为了跳过SSH首次连接的host key确认提示。这个参数和上面提到的StrictHostKeyCheckingaccept-new效果类似但是针对Ansible和paramiko的。如果你希望保留一定的安全性也可以用accept-new这类更精细的控制但很多运维为了方便直接关掉了检查。我个人的习惯是在测试环境关掉在生产环境保持开启或者用accept-new模式。4.2 ssh-agent与带密码的私钥前面提到给私钥设置密码会影响自动化流程的顺畅度。但在一些高安全要求的公司里SSH私钥必须设置密码这个问题怎么解决答案是ssh-agent。ssh-agent是一个运行在后台的守护进程用来缓存私钥。你只需要在登录控制端后执行一次ssh-add输入一次私钥密码之后的SSH连接都会自动使用agent里缓存的密钥不再需要重复输入密码。操作步骤是这样的eval $(ssh-agent -s) ssh-add ~/.ssh/id_ed25519执行ssh-add时会提示你输入私钥的密码输入后这个密码就会被缓存在agent里。之后你再运行ansible即使私钥是有密码的Ansible也会自动通过agent完成认证不影响自动化流程。不过要注意ssh-agent的缓存是临时的重启系统或者agent进程被杀掉就需要重新添加。如果你用的是图形化桌面可以把ssh-add放到登录启动项里如果是纯命令行的跳板机可以写一个启动脚本登录时自动执行。4.3 多密钥场景给不同环境指定不同的私钥运维碰到多环境是常态测试环境一套密钥、生产环境一套密钥、和第三方合作的项目可能还有单独的一套。如果你只有一套默认的id_rsa或id_ed25519那么每次连接不同的环境都要手动指定密钥特别容易搞混。解决这个问题有两种方式。一种是修改~/.ssh/config为不同的主机设置不同的IdentityFile。这个文件的作用是SSH客户端的自定义配置你可以在里面为一个域名或一组主机指定私钥、用户名、端口等信息Host test-server HostName 192.168.10.20 User root Port 22 IdentityFile ~/.ssh/id_ed25519_test Host prod-server HostName 192.168.20.30 User admin Port 2222 IdentityFile ~/.ssh/id_ed25519_prod配置完成后你只需要执行ssh test-server就可以连接到测试服务器不用再记IP、用户、端口和密钥文件路径。Ansible的host配置里也可以直接写ansible_ssh_private_key_file参数来单独指定某台主机的私钥[test] 192.168.10.20 ansible_ssh_private_key_file/root/.ssh/id_ed25519_test [prod] 192.168.20.30 ansible_ssh_private_key_file/root/.ssh/id_ed25519_prod这种灵活度在混合管理多个环境时极其重要不会因为一台机器用了不同密钥把所有连接都搞乱。4.4 复制文件到所有节点并授权一个实战案例免密登录配置好的第一个实战case就是我经常做的批量分发文件和设置权限。引用一条常见的需求“Ansible复制文件到所有节点并授权777权限”。这个需求在Ansible里可以用copy模块加file模块来实现。首先是在ansible.cfg里确认好inventory路径然后把这个需求写成一个playbook。比如要把一个脚本文件分发到所有web节点的/opt/scripts目录下并设置777权限--- - name: 批量分发脚本文件并设置权限 hosts: web tasks: - name: 确保目标目录存在 ansible.builtin.file: path: /opt/scripts state: directory mode: 0755 - name: 复制脚本到所有节点 ansible.builtin.copy: src: /path/to/local/script.sh dest: /opt/scripts/script.sh mode: 0777没有免密登录之前这个playbook根本跑不起来因为Ansible在连接每一台机器时都会卡在密码验证上。而有了免密登录之后一条ansible-playbook命令就能把文件推送过去并且立即设置好权限。你还可以用ad-hoc命令快速完成类似操作ansible web -m copy -a src/path/to/local/script.sh dest/opt/scripts/script.sh mode0777这里值得说明的是777权限在生产环境里要慎用尤其对于脚本文件这种权限意味着任何用户都能执行和修改它。我更推荐的做法是如果脚本需要执行权限用0755如果需要被特定服务读写用0640并配合属主属组设置。下面的实战中还会分享权限相关的坑。5. 常见问题与排查技巧实录5.1 登录时仍然要求输入密码这是免密配置后最常见的故障。公钥确实放进去了但还是需要密码排查思路按顺序来看。第一步确认控制端用的就是你要的那把私钥。很多人系统里有多个密钥文件SSH默认会尝试id_rsa或id_ed25519如果你的密钥文件名不是默认的连接时需要显式指定ssh -i ~/.ssh/my_key usertarget-server第二步检查目标服务器上~/.ssh/authorized_keys的权限。SSH对权限要求非常严格如果authorized_keys文件权限太开放比如是644SSH会拒绝使用它。具体来说~/.ssh目录的权限应该是700~/.ssh/authorized_keys文件的权限应该是600。如果发现不对可以用下面的命令修复chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys第三步确认目标服务器的SSH配置允许公钥认证。查看/etc/ssh/sshd_config中的PubkeyAuthentication是否为yes改完之后需要重启sshd服务。第四步确认你连接的账号和公钥放进的账号是同一个。比如你把公钥放到了root的authorized_keys里但连接时用的是普通用户user那当然连不上。5.2 连接超时或拒绝连接如果你发现不是认证问题而是连接根本建立了不可能是以下原因目标服务器没有启动SSH服务、防火墙拦住了22端口、网络本身不通或者自定义了SSH端口没有指定。排查命令很简单先用ping确认网络通不通再用telnet或nc测试端口能不能通nc -vz 192.168.1.101 22如果端口不通检查一下服务器防火墙规则。5.3 could not create directory .ssh 错误这个错误在Windows上使用Git Bash连接服务器时偶尔会遇到原因是Windows账户名有中文字符导致SSH在解析默认路径时出错。解决办法是显式指定.ssh目录的位置ssh -i /c/Users/yourname/.ssh/id_ed25519 usertarget-server或者设置HOME环境变量指向你的用户目录。这类问题在纯Linux环境里基本遇不到但在混合环境里很常见。5.4 批量执行时的host key确认卡死批量分发或者批量执行命令时如果第一次连接一台新的机器SSH会询问是否信任该主机的host key这会导致自动化脚本卡住。解决办法在之前的脚本里提过就是加StrictHostKeyCheckingaccept-new参数。需要注意这个参数要求OpenSSH版本在7.6以上如果你的客户端版本偏老就需要改用StrictHostKeyCheckingno但这个会完全跳过host key校验安全性差一些。5.5 排查清单速查表为了方便遇到问题时快速定位把常见问题整理成一个速查表。现象可能原因排查命令或解决方案免密登录仍然要求输密码公钥内容或权限问题检查authorized_keys内容和权限确认用的哪把私钥连接被拒绝SSH服务未启动或端口错误systemctl status sshdnc -vz检查端口连接超时防火墙规则或网络不通ping、nc -vz检查批量脚本卡在host key确认未关闭host key校验加StrictHostKeyCheckingaccept-new密钥正确但认证一直失败目标服务器sshd_config禁用公钥认证检查PubkeyAuthentication配置Ansible指定私钥不生效inventory文件未配置private_key_fileansible.cfg指定private_key_file或在hosts中指定ansible_ssh_private_key_filessh-add报错agent未启动未启动ssh-agenteval $(ssh-agent -s)6. 实操总结与经验心得做完这一整套SSH免密配置之后我最深的感受是自动化工具的流畅度往往取决于这些最基础的安全机制是否配置到位。Ansible本身并不复杂复杂的反而是它下面依赖的这些底层组件之间的配合。SSH密钥免密登录就像一条高速公路的入口入口通畅了后面的批量操作才能真正跑起来。有几个经验过来说一下。第一个是私钥的保管问题不管你怎么配置免密私钥文件本身一定要严格控制访问权限建议只允许当前用户读写chmod 600是一个基本的底线。第二个是修改生产环境的sshd_config之前务必先确认密钥登录已经稳定可用否则改完PasswordAuthentication no之后一旦密钥有问题你就被锁在外面了。第三个是遇到问题不要盯着一个方向硬查很多时候问题出在多个环节按顺序排查是最高效的。再分享一个小技巧在批量执行或者调试时可以给自己写一个快速验证脚本专门用来循环ping、检查端口、测试免密连接这样每次加入新节点或者怀疑网络有问题时一条命令就能把所有机器扫一遍。运行环境越复杂这种小工具就越能帮你节省时间。对于Ansible来说SSH免密登录只是万里长征第一步但这一步走扎实了后面的playbook编写、模块学习、角色定义、甚至整套自动化平台搭建都会顺畅很多。希望这篇内容能帮你把基础打牢后续遇到更复杂的自动化场景时不至于卡在最底层。