news 2026/11/2 9:09:32

OpenCloudOS与openEuler混合环境Ansible安装配置实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenCloudOS与openEuler混合环境Ansible安装配置实战

1. 先搞清楚:这两套系统上的 Ansible 到底难在哪

一批新机器上架,系统盘里装的不是同一个发行版——一部分是 OpenCloudOS,一部分是欧拉(openEuler),版本还横跨 8 和 9、20.03 和 22.03。机器到位之后,第一件事不是装业务,而是把基线统一掉:时区、时间同步、账号、防火墙策略、内核参数、常用工具包。这种活儿用脚本堆 bash 也能干,但干到第三轮你就会发现脚本里全是if [ -f xxx ]的判断,改一次跑一次都得重新校对。Ansible 就是我在这类场景下的首选:无代理、走 SSH、幂等、配置即文档,新同事接手看 inventory 和 playbook 就能明白这台机器被改过什么。适合谁看?手上有几台到几百台 Linux 机器、想用配置管理替代手工脚本、又不想引入重型平台的人。下面这些内容,是我在两套系统混合的环境里反复装、反复配、反复踩坑之后整理出来的东西,包含版本选型、安装路径、配置细节和报错处理,照着做基本能一次跑通。

1.1 同源不同枝:先认清两套发行版的差异点

OpenCloudOS 和欧拉都属于 RPM 体系、用 dnf 管包、systemd 管服务、配置文件路径高度一致,所以很多操作可以共用一套命令。但它们毕竟是两条独立的发行版线路,仓库里打好的软件包、默认 Python 版本、SELinux 默认策略、以及对第三方仓库的兼容程度,都存在肉眼可见的区别。这些区别不体现在日常dnf install上,而是集中爆发在 Ansible 控制节点的 Python 版本要求上——Ansible 的核心执行引擎是 Python 写的,控制节点版本不够,pip 直接给你一个Requires-Python >=3.9的红字,装都装不上。

具体到实际遇到的版本:欧拉 20.03 LTS 系列默认带的是 Python 3.7,22.03 LTS 系列普遍在 3.9 及以上;OpenCloudOS 8 系列默认 Python 3.6,9 系列则上到 3.9 甚至更高。而 ansible-core 从 2.14 开始把控制节点的门槛提到了 Python 3.9,2.16 之后进一步抬到 3.10。这意味着什么?意味着你在欧拉 20.03 上想直接 pip 装最新版 ansible-core,大概率是装不上的,要么退回系统仓库提供的老版本,要么自己额外装一个高版本 Python 解释器并给它单独建虚拟环境。这个判断必须在动手之前做完,否则后面全是返工。

被控节点的要求就宽松得多。Ansible 推模块过去执行时,目标机上只要有可用的 Python 3 解释器就够了,官方对较新版本的最低要求大致在 Python 3.6 上下,个别模块(比如某些依赖新版标准库的)会要求更高。老一点的系统如果只有 Python 2,就得在ansible.cfg里显式指定interpreter_python,或者干脆先在被控机上补一个 Python 3。我在一套老系统上就吃过这个亏:控制节点一切正常,ping模块能通(因为 ping 走的是 raw 方式,不需要 Python),但一跑dnf模块就报/usr/bin/python: not found,排查了半天才发现是解释器路径的问题。

1.2 无代理模式的取舍:为什么不用装 agent 的方案

Ansible 最吸引人的地方就是无代理。不需要在几百台机器上装一个常驻进程,不需要额外开监听端口,不需要担心 agent 版本和控制端不匹配,SSH 通就能干活。对于一套刚上架、还没接监控、防火墙策略也还没最终确定的环境来说,这个特性省掉的事情太多了——你不用担心 agent 包在某个发行版上没有对应版本,也不用为了升级 agent 去做一轮全量滚动。

代价也是实打实的。第一是性能,每次任务执行都要新建 SSH 连接(除非开了连接复用),几百台机器并发跑起来,控制节点的 CPU 和文件描述符都会被顶到高位。第二是依赖 SSH 的稳定性,网络抖一下、sshd 的 MaxStartups 卡一下,整个 play 就会挂掉几台。第三是没法做常驻式的事件驱动,Ansible 是推模式,你不跑它就不动,做不到 agent 那种实时上报。所以我的判断标准很简单:需要秒级响应和状态回传的场景,无代理方案不合适;需要批量下发配置、统一基线、做一次性变更的场景,Ansible 是最省心的选择。

1.3 控制节点和被控节点,角色千万别混

新手最容易犯的错是把控制节点也塞进 inventory 里当被控机用。技术上可行,但一旦你的 playbook 里写了重启服务、改防火墙、调内核参数这类操作,控制节点自己把自己玩挂,后面的任务就全断了。我现在的做法是严格分开:挑一到两台配置相对好的机器专门做控制节点,inventory 里用[control]单独分组,业务 playbook 的hosts永远指向业务分组,绝不写all。

角色数量建议配置要求关键依赖
控制节点1 主 1 备4 核 8G 起,磁盘留 50G 给日志和临时文件Python 3.9+、ansible-core、SSH 客户端
被控节点不限按业务定Python 3.6+、sshd、sudo
跳板/中继视网络分区而定低SSH 转发能力

控制节点的磁盘容易被忽略。ansible-playbook默认会在当前目录生成.retry文件,开了log_path之后日志会持续增长,加上临时传输的模块文件,跑上几个月就能吃掉几个 G。我一般会在ansible.cfg里把retry_files_enabled关掉,日志按月切割,这是后话,但规划阶段就该想到。

2. 动手之前,把环境摸清楚再装

装 Ansible 这件事本身只有几行命令,真正花时间的是前置确认。我见过太多"装完了跑不通"的情况,追根溯源八成是版本不匹配、账号权限没配好、或者网络根本不通。花二十分钟做一轮体检,比事后花两小时排查划算得多。

2.1 版本矩阵:先确定你能装哪个 Ansible

这一步的核心是搞清楚两件事:控制节点的 Python 版本,以及你想要哪个 Ansible 版本。这两个是硬约束,其余的都能商量。我的建议是优先保证控制节点能跑较新的 ansible-core,因为新版本在错误提示、模块兼容性、collection 管理上都好很多;如果系统 Python 实在上不去,就老老实实用系统仓库里的版本,不要硬凹。

发行版线路典型默认 Python可行的 ansible-core 版本区间推荐安装方式
欧拉 20.03 LTS3.72.9 ~ 2.12系统仓库安装
欧拉 22.03 LTS3.9 ~ 3.102.14 ~ 2.16虚拟环境 pip
OpenCloudOS 83.62.9 ~ 2.11系统仓库安装
OpenCloudOS 93.9 ~ 3.112.15 ~ 2.17虚拟环境 pip

表里的区间是经验值,实际以官方发布说明为准。有一个细节值得注意:ansible这个包和ansible-core不是一回事。前者是"全家桶",把大量常用 collection 打包在一起,装完体积好几百兆;后者只有核心引擎,collection 按需安装。在受控环境里我更倾向于用ansible-core加requirements.yml精确声明依赖,装什么、什么版本一目了然,不会因为某个 collection 悄悄升级导致 playbook 行为变化。

2.2 十条命令做完基础体检

下面这套检查我基本是条件反射地在每台新机器上跑一遍,几分钟的事,能挡掉后面绝大部分莫名其妙的故障。

cat /etc/os-release # 确认发行版和版本号 uname -r # 内核版本,判断是否需要额外驱动 python3 -V # 解释器版本,决定 Ansible 版本上限 which python3 # 解释器路径,后面写进配置 dnf repolist # 仓库是否可用、有没有缺 timedatectl # 时区和时间同步状态 hostnamectl # 主机名是否规范 ip -br a # 网卡与地址 df -h /var /opt # 磁盘余量 ulimit -n # 文件描述符上限

timedatectl这条别跳过。时间不同步会导致 SSH 认证偶发失败、日志时间线错乱、某些依赖时间戳的模块判定异常。我有一次在一批虚拟机上排查"随机几台连不上"的问题,最后发现是其中三台的时间漂了十几分钟,SSH 的密钥校验直接拒绝。

ulimit -n也要看。默认值可能是 1024,你后面想把forks调到 50,每个并发连接会占用若干文件描述符,加上 SSH 进程本身的开销,1024 很容易顶满,表现就是跑到一半报Too many open files。控制节点我一般调到 65535,写在/etc/security/limits.d/下的独立配置文件里,比改主配置安全。

2.3 运维账号与提权方式,提前定死

Ansible 连过去用什么账号、怎么提权,这两件事必须在第一批任务之前定下来,中途改会牵动所有 playbook。我的标准做法是:建一个专用运维账号,全环境同名,SSH 密钥登录,sudo 免密执行全部命令。

useradd -m -s /bin/bash deploy mkdir -p /home/deploy/.ssh && chmod 700 /home/deploy/.ssh # 把控制节点的公钥写入 authorized_keys chmod 600 /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh # 免密 sudo echo 'deploy ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/90-deploy chmod 440 /etc/sudoers.d/90-deploy visudo -c # 语法校验,别省这一步

注意:/etc/sudoers.d/下的文件权限必须是 0440 且属主为 root,权限不对 sudo 会直接忽略该文件,你会看到"明明写了却还是要密码"的诡异现象。

免密 sudo 听起来危险,但在受控的运维网段里,它换来的是自动化流程不中断。如果你所在的环境对安全要求更高,可以退一步只对特定命令免密,比如只放行systemctl、dnf、firewall-cmd这几个,其余保持密码。代价是 playbook 里不能出现任意 shell 命令,得改成白名单内的调用,写起来会啰嗦一些,但可控性更强。

3. 安装路线选型:三条路,各有各的适用场景

装 Ansible 的方式不止一种,选哪条路取决于你的网络环境、版本要求和运维习惯。我把常用的三条路都走了一遍,各自的坑也记录下来了。

3.1 系统仓库直装:快,但要接受版本偏旧

最省事的方式,两条命令结束战斗。

dnf makecache dnf install -y ansible ansible --version

优点是包由发行版维护,依赖关系、SELinux 策略、systemd 单元都替你处理好了,出问题也容易找到同版本的人。缺点是版本通常落后上游一到两年,一些新模块和新参数用不了。我在欧拉 20.03 和 OpenCloudOS 8 这类 Python 版本偏低的环境里基本都走这条路,装完是 2.9 系列,够用,写 playbook 的时候注意别用 2.10 之后才有的语法就行。

判断能不能用的标准很简单:ansible-doc里能查到你要用的模块,参数也对得上,那就没问题。实在需要新特性,可以在 playbook 里做版本判断,用when: ansible_version.full is version('2.12', '>=')分开写两条任务,比强升版本稳。

3.2 虚拟环境配合 pip:我最常用的方式

Python 3.9 以上的环境,我一律推荐这条路。核心思路是用 venv 隔离出一个独立的 Python 环境,Ansible 装在里面,不和系统包管理器打架,升级、回滚、并行装多个版本都很方便。

# 装 venv 和编译依赖(部分包需要) dnf install -y python3 python3-pip python3-devel gcc libffi-devel openssl-devel # 建环境,路径固定在 /opt 下方便统一管理 python3 -m venv /opt/ansible-venv source /opt/ansible-venv/bin/activate # 升级 pip,换成你所在网络里速度快的镜像源 pip install --upgrade pip -i https://<镜像源地址>/simple # 只装核心引擎,版本按前面对照表来 pip install "ansible-core==2.16.*" # 装常用 collection ansible-galaxy collection install ansible.posix community.general

隔离带来的好处在升级时最明显。有一次需要临时验证一个 playbook 在旧版本下的行为,我直接再建一个 venv 装 2.14,两套环境并存,激活哪个用哪个,完全不影响生产环境正在用的那套。如果当初是 dnf 装的,就得先卸再装,中间还可能把依赖链搞乱。

环境建好之后,把激活命令写进运维账号的.bashrc,或者干脆在/usr/local/bin/下放个软链指向 venv 里的可执行文件,这样不同登录会话都能用。我更喜欢后者,简单直接:

ln -sf /opt/ansible-venv/bin/ansible /usr/local/bin/ansible ln -sf /opt/ansible-venv/bin/ansible-playbook /usr/local/bin/ansible-playbook ln -sf /opt/ansible-venv/bin/ansible-galaxy /usr/local/bin/ansible-galaxy

注意:软链方式会让所有用户共用同一个 venv。如果不同团队需要不同版本,还是老老实实用激活脚本,或者给每条命令加绝对路径。

3.3 离线环境:把包搬进去

内网隔离环境没法直接联网装,这时候需要提前在一台同架构、同发行版的联网机器上把包下全。dnf 和 pip 都提供了下载依赖的能力。

# dnf:下载 ansible 及其全部依赖到本地目录 dnf install -y --downloadonly --downloaddir=/tmp/ansible-rpms ansible # 打包带走 tar -czf ansible-rpms.tar.gz -C /tmp ansible-rpms # 目标机解压后本地安装 tar -xzf ansible-rpms.tar.gz -C /tmp dnf install -y --disablerepo='*' /tmp/ansible-rpms/*.rpm

pip 那条路类似,用pip download把 wheel 包下全:

pip download ansible-core==2.16.6 -d /tmp/ansible-wheels -i https://<镜像源地址>/simple # 目标机 pip install --no-index --find-links=/tmp/ansible-wheels ansible-core==2.16.6

离线安装最容易翻车的地方是"依赖不全"。--downloadonly在某些仓库配置下会漏掉间接依赖,装的时候报缺包。我的经验是把目标机上dnf install的完整输出先跑一遍(用--assumeno让它在确认前停下),把依赖列表抄下来核对,或者直接在一台空白的同版本机器上验证一遍离线安装包,通过了再搬到内网。

4. 配置写对了,后面能省一半的事

Ansible 默认行为在单机试验时没问题,放到几十上百台机器的环境里就各种别扭:输出太长看不清、每次都要手动指定 inventory、连接超时太短、日志没地方存。这些问题都能通过一份ansible.cfg解决。

4.1 先约定目录结构

目录结构不是形式主义,它决定了半年后你还能不能快速找到某个变量是在哪里定义的。我固定用下面这套:

/opt/ansible/ ├── ansible.cfg ├── inventory/ │ ├── hosts.ini │ └── hosts-prod.ini ├── group_vars/ │ ├── all/ │ │ ├── main.yml │ │ └── vault.yml │ ├── webservers.yml │ └── dbservers.yml ├── host_vars/ │ └── node-01.yml ├── roles/ ├── playbooks/ │ ├── site.yml │ └── baseline.yml ├── templates/ ├── files/ ├── logs/ └── requirements.yml

关键在于ansible.cfg和 inventory 放在同一级,Ansible 会从当前工作目录开始向上查找配置文件。只要在/opt/ansible下执行命令,永远用的是这套配置,不会误用系统级的/etc/ansible/ansible.cfg。这个细节我踩过坑:有一次在/root下直接跑命令,加载的是系统默认配置,连的是另一套 inventory,把测试环境当生产跑了一遍,好在只是收集信息,没造成实质影响。

4.2 ansible.cfg 里值得动的那几个参数

默认配置大部分不用改,下面这几个是我每套环境都会调整的。

[defaults] inventory = ./inventory/hosts.ini remote_user = deploy host_key_checking = False forks = 30 timeout = 15 gathering = smart fact_caching = jsonfile fact_caching_connection = ./cache fact_caching_timeout = 86400 stdout_callback = yaml callbacks_enabled = timer, profile_tasks log_path = ./logs/ansible.log retry_files_enabled = False interpreter_python = auto_silent deprecation_warnings = False [privilege_escalation] become = True become_method = sudo become_user = root become_ask_pass = False [ssh_connection] pipelining = True ssh_args = -o ControlMaster=auto -o ControlPersist=300s -o PreferredAuthentications=publickey

逐个说理由。host_key_checking = False关掉的是首次连接时的指纹确认,在内网、机器频繁重建的环境里必须关,否则一批新机器上线,每个都要人工敲 yes,自动化就断了。代价是失去对中间人攻击的防护,所以我只在可控网段里这么设,跨公网的环境会保留校验并预置 known_hosts。

forks是并发数,默认 5。调到 30 是我在 4 核控制节点上的经验值,再往上 SSH 进程的创建开销开始显现,收益递减。这个值要和控制节点的 CPU 核数、ulimit -n、被控端 sshd 的MaxStartups一起看,单方面调大只会换来连接拒绝。

pipelining = True是性能优化里性价比最高的一项。开启后 Ansible 不再为每个模块单独创建临时文件并通过 SFTP 传输,而是把模块内容通过 SSH 的标准输入直接喂给远端 Python,减少往返次数。实测在中等规模环境里能省掉 30% 到 50% 的执行时间。但它有个前置条件:远端 sudo 不能要求 tty(requiretty)。老版本系统默认开着,需要在 sudoers 里关掉;新版本基本都默认关闭了。

fact_caching那块是给大规模环境准备的。默认每次都重新收集 facts,几百台机器光收集就要跑好几分钟。开了 jsonfile 缓存之后,一天内重复执行可以跳过收集,用gather_facts: no加缓存读取。缓存有效期设 86400 秒,超过就重新收,避免机器配置变了还拿着旧数据做判断。

stdout_callback = yaml纯粹是为了可读性。默认的default回调输出是一行 JSON 挤在一起,改完 yaml 之后每个任务的返回结构清晰展开,排错时能一眼看到是哪个字段不对。配合profile_tasks还能看到每个任务耗时,优化 playbook 的时候特别有用。

参数默认值建议值调整理由
forks520~50提升并发,需配套调 fd 上限
timeout1015~30跨网段连接留余量
pipeliningFalseTrue减少 SSH 往返,提速明显
host_key_checkingTrueFalse(内网)免交互,新机器频繁上线场景必需
gatheringimplicitsmart配合 facts 缓存
retry_files_enabledTrueFalse避免目录堆积 .retry 文件

4.3 inventory 的分组和变量分层

inventory 有两种写法,INI 和 YAML。小规模用 INI 顺手,规模上去之后 YAML 更好维护,尤其是嵌套分组和变量内联。我一般用 INI 做主清单,变量单独放group_vars。

[all:vars] ansible_python_interpreter=/usr/bin/python3 ansible_ssh_common_args='-o StrictHostKeyChecking=no' [control] ctrl-01 ansible_host=10.0.0.10 [webservers] web-[01:08] ansible_host=10.0.1.[11:18] [dbservers] db-01 ansible_host=10.0.2.11 db-02 ansible_host=10.0.2.12 [oe20] db-01 [ocos9] web-01 web-02 [all:children] webservers dbservers

这里有两个实用技巧。第一,主机名支持范围展开,web-[01:08]会自动生成八台,配合ansible_host的[11:18]一一对应,省得手敲。第二,我用发行版维度单独建了oe20和ocos9两个分组,因为这两套系统的包名、服务名偶尔有差异,做条件分支时按分组判断比按 facts 判断更快,也更直观。

变量优先级是另一个容易糊涂的地方。从低到高大致是:role defaults → inventory group_vars(all)→ inventory group_vars(具体组)→ inventory host_vars → play vars → task vars → 命令行-e。记住一条就够:范围越小优先级越高,命令行最高。调试时用ansible-inventory --host web-01和ansible -m debug -a var=xxx能看到最终生效的值,比猜快得多。

4.4 SSH 免密:别用密码硬扛

Ansible 支持密码认证,但每次连接都要输密码或者用--ask-pass,自动化就无从谈起。免密密钥是标配。

# 控制节点生成密钥,ed25519 比 rsa 短且快 ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519_ops # 分发到被控节点 ssh-copy-id -i ~/.ssh/id_ed25519_ops.pub deploy@10.0.1.11 # 验证 ssh -i ~/.ssh/id_ed25519_ops -o BatchMode=yes deploy@10.0.1.11 'echo ok'

BatchMode=yes这个参数很关键,它让 SSH 在需要交互时直接失败而不是卡住等输入。批量分发密钥的时候,如果某台机器的权限没配好,加了这个参数会立刻报错退出,不加就是整个脚本挂在那里等超时。

密钥分发完之后,先别急着关密码登录。我一般会留一周的观察期,确认所有自动化流程都走通了,再统一在 sshd 配置里关掉PasswordAuthentication,重启服务前用另一个会话验证一遍能连上,避免把自己锁在外面。

5. 从 ping 通到真正跑起来

安装和配置都到位之后,先用最简单的命令验证链路,再逐步增加复杂度。这个顺序不能颠倒,尤其是几十台机器的环境,一次性上复杂 playbook,出问题根本定位不到是哪一层。

5.1 连通性验证的三板斧

第一条命令永远是 ping 模块。注意这个 ping 不是 ICMP,是 Ansible 自带的一个探活模块,它会连过去、推一小段 Python 代码、拿到返回,能验证 SSH 通、Python 可用、权限够。

ansible all -m ping

返回pong说明链路没问题。如果这里就挂了,先别往下走,把报错信息看清楚:UNREACHABLE是网络或 SSH 层,FAILED一般是 Python 或权限层,两条路排查方向完全不同。

第二条是收集 facts,顺便看看两套系统的差异。

ansible all -m setup -a 'filter=ansible_distribution*' ansible all -m setup -a 'filter=ansible_python*'

输出会告诉你每台机器的发行版、版本号、Python 路径。这一步在混合环境里特别重要,因为后面的条件判断全靠这些字段。

第三条是确认 inventory 解析正确。

ansible-inventory --graph ansible-inventory --list | head -50

--graph用树形展示分组关系,一眼能看出有没有主机挂错组;--list输出完整的变量合并结果,排查变量覆盖问题很有用。

5.2 一个可以直接抄的初始化 playbook

链路通了之后,先做一个不改动任何业务配置的基线 playbook,只做时间同步、基础工具、防火墙策略这几件低风险的事。

--- - name: 基础环境基线初始化 hosts: all become: true gather_facts: true vars: ntp_server: "ntp.internal.example" base_packages: - vim-enhanced - tar - chrony - firewalld - python3-firewall tasks: - name: 校验发行版家族,防止误操作 ansible.builtin.assert: that: - ansible_facts['os_family'] == "RedHat" fail_msg: "当前主机不是 RPM 体系,终止执行" success_msg: "系统家族校验通过" - name: 安装基础工具包 ansible.builtin.dnf: name: "{{ base_packages }}" state: present - name: 渲染 chrony 配置 ansible.builtin.template: src: templates/chrony.conf.j2 dest: /etc/chrony.conf owner: root group: root mode: '0644' backup: true notify: 重启 chronyd - name: 启动并设置开机自启 ansible.builtin.service: name: "{{ item }}" state: started enabled: true loop: - chronyd - firewalld - name: 放行 SSH 服务 ansible.posix.firewalld: service: ssh permanent: true state: enabled immediate: true handlers: - name: 重启 chronyd ansible.builtin.service: name: chronyd state: restarted

第一段assert是我后来加的习惯。有一次把 playbook 误指向了一台装有容器运行时的机器,包管理器不是 dnf,结果任务跑到一半失败,留下半截状态。加了这个断言之后,不匹配的机器直接被跳过,一行报错说清楚原因,比执行到中间崩掉强得多。

template任务里的backup: true也值得说。它会在目标文件旁边生成一个带时间戳的备份,改错了能立刻还原。对于/etc/chrony.conf这种改错了会导致时间同步失效、进而影响一堆依赖时间的组件的文件,多留一份备份的成本可以忽略。代价是目录里会堆备份文件,定期清理就行。

notify配合handlers是 Ansible 的经典设计:只有template任务真的产生了变更(文件内容有差异)才会触发 handler,配置没变就不重启服务。这个机制避免了每次跑 playbook 都无脑重启一遍服务,在几百台机器的规模下差别巨大。

ansible.posix.firewalld这个模块不在核心包里,需要提前装 collection:

ansible-galaxy collection install ansible.posix

如果环境不允许装 collection,退一步用ansible.builtin.command调firewall-cmd,但要做幂等判断,比如先查--query-service再决定是否放行,写法会啰嗦不少。

5.3 幂等性:判断 playbook 写得好不好的硬指标

同一个 playbook 连跑两遍,第二遍的changed应该是 0。做不到这一点的任务,迟早会在某次批量执行中给你制造意外。最常见的破坏幂等的写法就是滥用 shell。

# 反面例子:每次都执行,每次都报 changed - name: 解压安装包 ansible.builtin.shell: tar -xzf /tmp/app.tar.gz -C /opt

正确写法是用专门的模块,或者加上creates参数让它在目标已存在时跳过。

# 写法一:用专门模块 - name: 解压安装包 ansible.builtin.unarchive: src: /tmp/app.tar.gz dest: /opt remote_src: true # 写法二:必须用 shell 时加 creates - name: 初始化数据库 ansible.builtin.shell: /opt/app/bin/init-db.sh args: creates: /opt/app/data/.initialized

creates指定一个文件路径,存在就跳过执行,这是给一次性操作打标记的通用手法。removes是反过来的逻辑。这两个参数在必须调外部脚本时特别有用。

还有一个容易被忽略的点:文件权限和属主。copy、template、file这些模块如果只写了path和state,没写mode,那它就不会去改权限,但也不会报错,你会以为改好了其实没动。养成习惯,凡是涉及文件的模块,mode一定显式写出来,用字符串形式'0644'而不是数字644——数字形式在某些版本里会被当成八进制之外的解析,导致权限变成一堆乱七八糟的值。

5.4 密码和密钥交给 vault

playbook 里迟早要出现密码、密钥、token。明文写在group_vars里再提交到版本库,是安全审计里最常见的扣分项。Ansible 自带的 vault 能解决这个问题,加密后的文件可以放心提交。

# 新建一个加密文件 ansible-vault create group_vars/all/vault.yml # 编辑已有文件 ansible-vault edit group_vars/all/vault.yml # 加密单个字符串,直接贴进 yml ansible-vault encrypt_string '实际密码' --name 'db_password' # 运行时提供密码 ansible-playbook site.yml --ask-vault-pass

密码本身怎么管?--ask-vault-pass每次手输,适合低频操作。高频执行用密码文件,权限设成 0400,路径通过vault_password_file写进ansible.cfg,或者用环境变量ANSIBLE_VAULT_PASSWORD_FILE指过去。密码文件本身绝对不能进版本库,.gitignore里第一行就该是它。

6. 报错现场:这些坑我基本都趟过

Ansible 的报错信息有时代码很长,但真正有用的就一两行。下面按类别整理了高频问题,附上我的定位思路。

6.1 连不上:SSH 层的五种典型状况

UNREACHABLE! => {"msg": "Failed to connect to the host via ssh: Permission denied (publickey)."}

公钥没分发到、或者属主权限不对。检查目标机/home/deploy/.ssh/是不是 700、authorized_keys是不是 600、属主是不是 deploy。SSH 对权限极其敏感,权限过宽会直接忽略密钥文件,而且不给你任何提示。

UNREACHABLE! => {"msg": "Host key verification failed."}

host_key_checking没关,或者换了机器但 known_hosts 里还留着旧指纹。清理对应条目或者直接关掉校验。机器频繁重建的环境,建议把ansible_ssh_common_args里的StrictHostKeyChecking也一并设成 no,双保险。

UNREACHABLE! => {"msg": "ssh: connect to host 10.0.1.11 port 22: Connection timed out"}

网络不通或者 sshd 没起来。先用nc -zv 10.0.1.11 22确认端口,再登过去看服务状态。别一上来就怀疑 Ansible 配置,九成是网络或服务的问题。

ssh_exchange_identification: Connection closed by remote host

并发太高把被控端的 sshd 顶了。检查/etc/ssh/sshd_config里的MaxStartups,默认是10:30:100,意思是超过 10 个未认证连接开始随机拒绝,到 30 个拒绝一半,100 个全拒。控制节点forks开到 50 的时候这个默认值肯定不够,得调到30:60:200之类。同时看下MaxSessions,单连接多路复用时也会受限。

Too many open files

控制节点的文件描述符上限不够。ulimit -n确认,改/etc/security/limits.d/下的配置,注意要重新登录才生效,ulimit命令临时改只对当前 shell 有效。

6.2 Python 解释器引发的连锁反应

FAILED! => {"msg": "The module failed to execute correctly, you probably need to set the interpreter."}

最典型的解释器问题。原因是被控机上python3不在默认 PATH,或者干脆只有python。ansible.cfg里设interpreter_python = auto_silent能让 Ansible 自己去探测,探测不到就静默失败。稳妥做法是在 inventory 的[all:vars]里显式指定:

[all:vars] ansible_python_interpreter=/usr/bin/python3

如果某些机器的路径不一样,在host_vars里单独覆盖。我曾经遇到一台机器上/usr/bin/python3是个指向不存在目标的软链,实际解释器在/usr/local/bin/python3.9,这种就只能单机覆盖。

SyntaxError: invalid syntax

这条出现在模块执行阶段,通常是 Python 版本太低,跑不了新版本 Ansible 生成的模块代码。ansible-core 2.15 之后对目标节点的 Python 要求提高了一截,老系统上要么降 Ansible 版本,要么给目标机补一个新解释器并用interpreter_python指过去。

6.3 模块依赖缺失:一张表查完

很多模块不是纯 Python 实现的,它依赖系统里对应的 Python 绑定包。控制节点上装好了没用,被控节点也得有。

报错关键词缺失依赖处理方式
No module named 'firewall'python3-firewalldnf install -y python3-firewall
No module named 'dnf'python3-dnfdnf install -y python3-dnf
No module named 'selinux'python3-libselinuxdnf install -y python3-libselinux
Failed to import the required Python library (psutil)python3-psutildnf install -y python3-psutil
cryptography is required对应 wheel 包pip install cryptography到目标解释器

这张表我贴在运维文档里,新人遇到直接查。更省事的做法是把这些包写进基线 playbook,用dnf模块统一装上,新机器上线自动具备。

提示:装依赖包本身也得用 dnf 模块,而 dnf 模块依赖 python3-dnf。如果目标机连这个都没有,就只能先用raw模块装依赖,因为raw是唯一不需要 Python 的模块。这个先后顺序在最精简的系统镜像上很关键。

6.4 并发调大之后的性能反噬

forks从 5 调到 50 之后,很多人会遇到"执行更快了但也更容易失败"的情况。这不是错觉,背后有几个具体原因。

一是 SSH 连接建立的瞬时压力。50 个并发意味着控制节点同时发起 50 次 TCP 握手和密钥交换,CPU 会有一个明显的尖峰。控制节点配置不够的话,尖峰期间其他操作都会变慢。二是被控端 sshd 的连接数限制,前面提过的MaxStartups。三是网络设备,某些交换机或安全组对新建连接速率有限制,超了就丢包。

我的调优顺序是:先开pipelining和ControlPersist,这两个是纯收益,没有副作用;再逐步调forks,每次加 10,观察执行时间和失败率,找到拐点;最后才考虑上多控制节点分片。绝大多数中小规模环境,pipelining加forks=30就已经足够了,没必要追极限。

连接复用那里ControlPersist=300s的意思是 SSH 保持连接 5 分钟,期间同一台机器的多个任务复用这条连接,省掉重复握手。跑长 playbook 时效果非常明显,实测能把执行时间砍掉近一半。

7. 跑顺之后,让它长期能用

一次性把机器配好只是开始,真正的考验是半年后你还能不能快速改一个配置、加一批机器、回滚一次变更。

7.1 用 role 把重复的东西收起来

所有任务都堆在一个 playbook 里,写到第三个环境你就想重写。role 是官方的拆分方案,每个 role 管一件事,变量、模板、任务都在自己的目录里。

# 生成骨架 ansible-galaxy role init roles/common

生成的结构里,defaults/main.yml是默认变量(优先级最低,方便覆盖),vars/main.yml是高优先级变量(一般放不希望被覆盖的常量),tasks/main.yml是任务入口,handlers、templates、files各归各位。调用的时候在site.yml里引用:

--- - name: 全站基线 hosts: all roles: - common - name: Web 层 hosts: webservers roles: - common - nginx

写 role 的经验是:一个 role 只做一件能被一句话描述清楚的事。common管基础包和时区,chrony管时间同步,hardening管安全基线,别把三样混在一起。混在一起之后,你会遇到"我只想跑时间同步但不想动安全配置"的场景,然后就得靠tags补救,越补越乱。

依赖用requirements.yml声明,装的时候一条命令:

collections: - name: ansible.posix version: ">=1.5.0" - name: community.general version: ">=7.0.0" roles: - name: common src: <内部代码仓库地址> version: v1.2.0
ansible-galaxy install -r requirements.yml

版本号一定要写死或者写最小范围,别留空。留空意味着每次安装都拉最新,某天上游发了个不兼容的版本,你的 playbook 就会在没有任何改动的情况下突然挂掉。这种问题排查起来极其难受,因为"我什么都没改"。

7.2 执行记录、审计与回滚准备

log_path只是最基础的一层。生产环境的变更,我还会额外做三件事。

第一件是保留完整的执行日志,带时间戳和操作人。

mkdir -p logs ansible-playbook -i inventory/hosts.ini playbooks/site.yml \ -f 30 \ 2>&1 | tee "logs/$(date +%Y%m%d-%H%M%S)-baseline.log"

第二件是用--check --diff先看一遍影响面。--check是空跑,只报告会做什么变更不真的执行,--diff会把文件内容的前后差异打出来。这两个参数组合起来,等于给变更做了一次预演。

ansible-playbook -i inventory/hosts.ini playbooks/site.yml --check --diff --limit webservers

注意--check不是万能的,有些模块不支持检查模式,会直接跳过或者报不支持,比如某些command任务。第一次用的 playbook,我建议先在单台测试机上真跑一遍,再上--check看全局。

第三件是回滚预案。涉及配置文件的变更,我会要求对应的模板任务开backup: true,并在 playbook 里加一个rollback变量,出问题的时候用-e rollback=true切换到恢复分支。更重的变更(比如升级系统包)会提前打快照或者保留旧版本包,dnf history能查到每次事务的 ID,用dnf history undo <id>回退,这条命令救过我一次。

7.3 变更节奏:先灰度再全量

有了工具之后最大的诱惑就是"一把梭"。几百台机器一条命令全推,确实爽,但一旦有问题就是全站故障。我的固定节奏是:单台验证 → 小批量灰度 → 全量。

# 第一步:单台 ansible-playbook playbooks/site.yml --limit web-01 # 第二步:一个分组里挑三台 ansible-playbook playbooks/site.yml --limit 'web-[01:03]' # 第三步:观察指标,确认无异常 # 第四步:全量 ansible-playbook playbooks/site.yml -f 30

--limit支持模式匹配,web-*、webservers:!web-01(排除某台)、oe20:&dbservers(两个分组的交集),这些写法在灰度时特别方便。&是交集,!是排除,:是并集,记住这三个符号够用了。

还有一个我后来加上的习惯:给风险较高的任务打tags,全量执行时跳过,需要的时候单独跑。

- name: 重启业务服务 ansible.builtin.service: name: app state: restarted tags: [never, restart-app]

never是个特殊标签,意思是"默认永不执行",必须显式--tags restart-app才会跑。这个配合灰度流程非常好用,日常基线执行不会误重启业务。

ansible-playbook playbooks/site.yml --tags restart-app --limit web-01

.retry文件在这里也有用武之地。全量执行时有几台失败,Ansible 会生成site.retry记录失败的主机,修好问题之后用--limit @site.retry只重跑这几台,不用全量再来一遍。不过我在配置里关掉了自动生成,需要的时候手动加--retry-files或者就地--limit @xxx.retry,避免目录里堆一堆过期的 retry 文件。

8. 关于混合环境的一点个人习惯

两套发行版混着跑,最容易出问题的地方不是安装,而是"我以为它们一样"。包名差一个字、服务单元名不一样、默认路径挪了位置,这些差异在小规模测试时根本暴露不出来,等到几百台一起跑才会集中爆发。我现在的做法是把差异显式写进变量层,而不是散落在任务里做when判断:按发行版分组建group_vars,把包名列表、服务名、配置文件路径这些易变项全部提到变量里,任务本身保持干净。这样新增一个发行版线路,只需要加一个变量文件,playbook 一行都不用改。另一件事是每次动 playbook 之前先在单台上--check --diff跑一遍,养成这个习惯之后,很多低级错误在到达生产环境之前就被拦住了。踩过的坑多了就会发现,配置管理工具的价值不在于"快",而在于"改之前你能知道会发生什么"。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 9:08:44

AI写3D游戏实测:三大代码模型生成Three.js寻宝迷宫对比

最近我把 Step 5 Preview、DeepSeek V4 Pro 和 GLM5.3 拉到同一条起跑线上&#xff0c;让它们分别写同一个 3D 游戏。任务很简单粗暴&#xff1a;一个基于 Three.js 的浏览器 3D 寻宝迷宫&#xff0c;第一人称视角&#xff0c;有碰撞、有胜利判定、还要求能塞进手机浏览器里跑得…

作者头像 李华
网站建设 2026/11/2 9:08:20

基于斑马优化算法的风电功率预测模型优化

1. 项目背景与核心价值风电功率预测在新能源并网调度中扮演着关键角色。传统BP神经网络因其强大的非线性拟合能力被广泛采用&#xff0c;但存在收敛速度慢、易陷入局部最优等问题。斑马优化算法(ZOA)作为新型仿生优化算法&#xff0c;通过模拟斑马群觅食行为中的社会等级机制和…

作者头像 李华
网站建设 2026/11/2 9:05:32

OpenCV任意角度图像旋转原理与工程实践指南

简介&#xff1a;本资源是一份基于OpenCV实现图像旋转的完整C工程实践包&#xff0c;面向计算机视觉初学者、图像处理学习者及C开发人员&#xff0c;解决图像方向调整、预处理与增强中的核心旋转操作问题。压缩包共14个文件&#xff0c;包含核心源码myRoteteImage.cpp、可执行程…

作者头像 李华
网站建设 2026/11/2 9:05:17

PDFLIB PDI 实战指南:PDF 解析、图像提取与文本坐标定位

简介&#xff1a;本资源是一份基于Visual Studio 2010平台、使用PDFLIB TET库实现PDF文件读取的完整C开发实践项目&#xff0c;面向具备基础C/C编程能力的开发者&#xff0c;解决PDF文本与图像提取、元数据获取及页面布局解析等实际工程需求。压缩包共49个文件&#xff0c;涵盖…

作者头像 李华
网站建设 2026/11/2 9:04:47

PyFlink向量化UDF性能优化与实战指南

1. 向量化UDF的本质&#xff1a;Arrow与Pandas的化学反应第一次在PyFlink生产环境看到向量化UDF性能报表时&#xff0c;我盯着那个比传统UDF快8倍的数字愣了半天。这种性能飞跃的背后&#xff0c;是Apache Arrow列式内存格式与Pandas向量化计算的完美配合。1.1 Arrow批传输的底…

作者头像 李华
网站建设 2026/11/2 9:03:09

Cursor 联动蓝湖 MCP:Vue 3 + SCSS 设计稿自动还原实战

1. 这个项目到底在解决什么问题设计师追着问“还原了吗”&#xff0c;大概是每个前端都经历过的日常。页面刚联调完&#xff0c;设计稿上标注的间距是 24px&#xff0c;你写的是 24px&#xff0c;但设计师打开一看&#xff0c;说“感觉不对”。你截图对比&#xff0c;发现字体渲…

作者头像 李华