
1. 项目概述为什么 Ansible 角色必须“被测试”而不是“被相信”在运维和基础设施即代码IaC实践中Ansible 角色Role早已不是可有可无的锦上添花而是整个自动化交付链路的基石单元。你写了一个nginx角色它能一键部署反向代理你封装了一个postgresql角色它能初始化数据库集群你甚至开发了跨云平台的k8s-worker角色统一纳管不同厂商的节点注册逻辑——但问题来了这些角色真的在所有目标环境中都可靠吗当 Ubuntu 18.04 升级到 20.04 后apt源配置是否还兼容当python3-venv包名在新版本中变更你的pip模块安装步骤会不会静默失败当团队里新同事直接ansible-galaxy install -r requirements.yml拉取你三个月前写的redis角色他敢不敢把它用在生产数据库服务器上这就是连续测试Continuous Testing要解决的核心问题不让“我本地跑通了”成为上线的唯一依据而让“每次提交都自动验证行为一致性”成为默认动作。标题里提到的 Molecule Travis CI 组合不是为了堆砌技术名词而是构建了一套轻量、隔离、可复现的验证闭环Molecule 负责在容器或虚拟机中“真实运行”你的角色不是 mock不是 dry-runTravis CI 则把这套验证变成每次git push后自动触发的守门员。Ubuntu 18.04 的指定恰恰说明这不是一个泛泛而谈的教程——它是对一个具体、稳定、仍在广泛使用的 LTS 环境的精准锚定。很多团队至今仍以 Ubuntu 18.04 为跳板过渡到更新版本它的内核、Python 版本3.6.9、systemd 行为、apt 仓库结构都构成了角色行为的隐式契约。跳过这个环境做测试等于在模拟器里练完车直接上高速——表面流畅实则风险暗藏。所以这个项目本质是给 Ansible 角色装上“安全气囊”它不改变你写 YAML 的习惯不强制你学新语法但要求你在tasks/main.yml旁边多放一个molecule/default/目录多写几行verify.yml的断言多配一个.travis.yml的触发规则。它解决的不是“能不能用”而是“敢不敢用”。尤其当你开始在 GitLab CI 或 GitHub Actions 迁移时这套基于 Ubuntu 18.04 的 Molecule 测试骨架就是你迁移成本最低、兼容性最强的起点。我见过太多团队在角色仓库里只放README.md和defaults/main.yml结果一次apt update就导致整个集群的 NTP 配置被覆盖——而这些问题本可以在molecule test的第三轮converge阶段就被捕获。2. 整体设计思路与工具选型逻辑为什么是 Molecule Travis CI Ubuntu 18.042.1 不选 Ansible 自带的--check或--diff因为它们只是“预演”不是“实测”很多人误以为ansible-playbook site.yml --check就是测试这是最大的认知偏差。--check模式下Ansible 会跳过所有command、shell、script模块的实际执行对apt、yum、copy等模块也仅做“状态预测”它无法验证apt是否真能从http://archive.ubuntu.com/ubuntu/dists/bionic/main/binary-amd64/Packages.xz下载元数据template模块生成的/etc/nginx/sites-available/default文件其变量渲染后是否语法合法Nginx 配置错误会导致nginx -t失败service模块启用nginx后端口80是否真被监听curl -I http://localhost是否返回200 OK。这些只有让角色在真实的 Ubuntu 18.04 环境中完整走完create → converge → verify → destroy生命周期才能确认。Molecule 正是为此而生它不是一个测试框架而是一个场景编排引擎。它不关心你用apt还是dnf只负责拉起一个干净的 Ubuntu 18.04 容器注入你的角色执行 playbook再运行你定义的验证脚本通常是testinfra或goss。这种“环境即测试靶场”的思路比任何静态分析都更贴近生产。2.2 为什么 Travis CI 是当时2018–2021最自然的选择而非 GitHub Actions标题明确指向 Travis CI这并非偶然。在 Ubuntu 18.04 作为主流 LTS 的年代2018.04–2023.04Travis CI 的trusty14.04、xenial16.04、bionic18.04镜像支持是开箱即用的。你只需在.travis.yml中写dist: bionic sudo: requiredTravis 就会为你分配一台预装好 Docker、Python 3.6、Git 的 Ubuntu 18.04 虚拟机。而 GitHub Actions 在 2019 年初刚发布时其ubuntu-latest默认指向 18.04但ubuntu-18.04runner 的稳定性、Docker 权限、网络策略远不如 Travis 成熟。更重要的是Travis 的cache机制对 Python pip 包缓存极其友好molecule test每次都要pip install molecule docker testinfra没有缓存的话单次测试耗时会从 2 分钟飙升到 8 分钟以上。我们实测过在 Travis 上开启pip缓存后molecule test的平均耗时稳定在 1分45秒左右而裸跑则波动在 6–12 分钟。这种时间成本直接决定了开发者是否愿意把molecule test加入本地 pre-commit 钩子。2.3 为什么死守 Ubuntu 18.04三个不可妥协的理由内核与 systemd 兼容性Ubuntu 18.04 使用 Linux kernel 4.15 和 systemd 237。很多角色依赖systemctl is-active nginx或journalctl -u nginx --since 1 hour ago做状态检查。kernel 5.4 的 cgroup v2 默认启用会导致某些旧版docker容器内systemd启动失败而 Travis 的bionic镜像完美规避此问题。Python 生态锁定Ansible 2.8–2.92019–2020 主流版本官方推荐 Python 3.6。Ubuntu 18.04 自带python3.6且pip3源默认指向https://pypi.org/simple/无需额外配置pip.conf。若强行切到 20.04python3.8的asyncio行为变化曾导致community.general插件中的wait_for模块超时异常。APT 仓库结构稳定性http://archive.ubuntu.com/ubuntu/dists/bionic/的目录结构三年未变apt update命令成功率接近 100%。而 20.04 的focal仓库在 2020 年曾因cloud-init包签名密钥轮换导致大量apt update报NO_PUBKEY错误需要手动apt-key adv --keyserver keyserver.ubuntu.com --recv-keys XXXX。这种非角色本身的外部依赖故障会污染测试结果让开发者误判角色缺陷。提示如果你现在2024年想复用此方案请将dist: bionic替换为dist: focal或jammy并同步升级molecule到 4.xtestinfra到 6.x。但本文严格遵循标题所有命令、路径、配置均以 Ubuntu 18.04 为唯一基准。3. 核心细节解析与实操要点Molecule 的四大生命周期与关键配置3.1 Molecule 的生命周期create → converge → verify → destroy每一步都在模拟真实交付Molecule 的强大在于它把一次完整的角色验证拆解为四个原子阶段每个阶段都可独立调试、可定制驱动、可注入钩子。这不是黑盒测试而是白盒流水线create调用docker驱动启动一个 Ubuntu 18.04 容器。容器名格式为instance-name-timestamp确保并发测试不冲突。此阶段会自动挂载当前角色目录到容器/tmp/molecule/role-name/并设置ANSIBLE_ROLES_PATH/tmp/molecule让 Ansible 能正确解析include_role。converge这是核心。Molecule 自动生成一个converge.ymlplaybook内容极简- name: Converge hosts: all become: true roles: - role: your-role-name它会执行ansible-playbook converge.yml -i /tmp/molecule/your-role-name/inventory。注意become: true是默认开启的因为绝大多数角色需要 root 权限操作 apt、systemd。如果你的角色明确不需要提权如纯文件模板生成可在molecule.yml中设become: false。verifyMolecule 不内置断言逻辑而是调用外部验证器。默认是testinfra它把 Python 断言翻译成 Ansible 模块调用。例如验证 Nginx 是否监听 80 端口# tests/test_default.py def test_nginx_is_installed(host): assert host.package(nginx).is_installed def test_nginx_is_running_and_enabled(host): assert host.service(nginx).is_running assert host.service(nginx).is_enabled def test_nginx_listens_on_port_80(host): assert host.socket(tcp://0.0.0.0:80).is_listeningtestinfra会通过host.run(ss -tln | grep :80)执行并解析 stdout。这种“用 Ansible 模块做断言”的设计保证了验证逻辑与角色执行逻辑完全同源避免了 shell 脚本与 Ansible 模块行为不一致的陷阱。destroy删除容器清理/tmp/molecule/下的临时文件。此阶段失败不会导致整个测试失败--destroyalways可强制但建议保留避免 Travis 构建机磁盘被残留容器占满。3.2molecule.yml配置详解驱动、平台、场景的三重控制molecule.yml是 Molecule 的“宪法”它定义了测试如何运行。以下是针对 Ubuntu 18.04 的最小可行配置已去除注释仅保留关键字段--- dependency: name: galaxy driver: name: docker platforms: - name: instance image: geerlingguy/docker-ubuntu1804-ansible:latest privileged: false pre_build_image: false provisioner: name: ansible inventory: host_vars: instance: ansible_python_interpreter: /usr/bin/python3 verifier: name: testinfra options: sudo: true逐项解析driver.name: docker明确使用 Docker 驱动。Molecule 还支持vagrant、ec2、openstack但 Docker 是 Travis CI 上唯一可行的选项Vagrant 在 CI 中启动虚拟机太慢EC2 需要 AWS 凭据。platforms.image这是关键不能直接写ubuntu:18.04因为官方镜像不含python3-pip、ansible、ssh服务。必须使用社区维护的geerlingguy/docker-ubuntu1804-ansible镜像它预装了openssh-serverMolecule 通过 SSH 连接容器python3.6pip3ansible2.8.x与 Ubuntu 18.04 兼容的最佳版本rsync用于高效同步文件ansible_python_interpreterUbuntu 18.04 默认python指向python2.7而 Ansible 2.8 强制要求 Python 3。此配置确保 Ansible 在容器内使用/usr/bin/python3避免module_stderr报错No module named ansible。verifier.options.sudo: truetestinfra默认以普通用户运行但host.package()、host.service()需要 root 权限读取系统状态。此配置让testinfra在执行时自动加sudo。注意geerlingguy/docker-ubuntu1804-ansible:latest镜像在 2023 年已停止更新但其20210101tag 仍可稳定拉取。若遇pull access denied可自行docker build一个基础镜像Dockerfile 仅需三行FROM ubuntu:18.04 RUN apt-get update apt-get install -y python3-pip openssh-server rsync pip3 install ansible2.8.20 CMD [/usr/sbin/sshd, -D]3.3tests/test_default.py编写原则从“能跑通”到“行为正确”的跃迁很多新手写测试只停留在assert host.package(nginx).is_installed这远远不够。一个健壮的测试应覆盖三层存在性Existence软件包、服务、配置文件是否存在状态性State服务是否运行、启用进程是否监听端口文件权限是否正确功能性Functionality服务是否提供预期功能例如 Nginx 返回 200PostgreSQL 能接受psql -c SELECT version();查询。以nginx角色为例一个生产级的test_default.py应包含import pytest def test_nginx_package(host): 验证 nginx 包已安装且版本符合预期 pkg host.package(nginx) assert pkg.is_installed # Ubuntu 18.04 默认 nginx 版本为 1.14.0若角色指定了更高版本此处需校验 assert pkg.version.startswith(1.14.) def test_nginx_service(host): 验证 nginx 服务状态 svc host.service(nginx) assert svc.is_running assert svc.is_enabled # 额外验证nginx 进程数是否合理避免 fork 爆炸 assert len(host.process.filter(commnginx)) 2 def test_nginx_config(host): 验证主配置文件语法正确且权限安全 f host.file(/etc/nginx/nginx.conf) assert f.exists assert f.user root assert f.group root assert f.mode 0o644 # 语法检查调用 nginx -t捕获 stdout/stderr cmd host.run(nginx -t) assert cmd.rc 0, fnginx config syntax error: {cmd.stderr} def test_nginx_http_response(host): 验证 nginx 功能性响应 # 使用 curl 检查 HTTP 头 cmd host.run(curl -I http://localhost) assert cmd.rc 0 assert HTTP/1.1 200 OK in cmd.stdout # 检查默认欢迎页内容可选但能防配置被覆盖 welcome host.file(/var/www/html/index.nginx-debian.html) assert welcome.exists assert Welcome to nginx in welcome.content_string关键技巧使用pytest.mark.parametrize对多个端口、多个配置文件做批量断言host.run()的rcreturn code必须显式断言否则命令失败会被忽略避免在测试中写time.sleep(2)等硬编码等待改用host.run(timeout 10s bash -c while ! curl -f http://localhost; do sleep 1; done)实现弹性等待。4. 实操过程与核心环节实现从零搭建可运行的 CI 测试流水线4.1 初始化 Molecule 环境四步完成角色测试骨架假设你已有一个名为ansible-role-nginx的 Git 仓库目录结构为ansible-role-nginx/ ├── defaults/ ├── files/ ├── handlers/ ├── meta/ ├── tasks/ ├── templates/ └── vars/现在按顺序执行以下命令全部在 Ubuntu 18.04 本地或 Travis 构建机中第一步安装 Molecule 及依赖# Ubuntu 18.04 自带 python3-pip无需安装 python sudo apt update sudo apt install -y python3-pip python3-docker # 创建虚拟环境隔离依赖强烈推荐避免与系统 pip 冲突 python3 -m venv ~/.molecule-env source ~/.molecule-env/bin/activate pip install --upgrade pip pip install molecule[docker] testinfra实测心得molecule[docker]是关键。方括号表示安装docker额外依赖它会自动安装docker-py即dockerPython SDK。若漏掉molecule create会报No module named docker。testinfra必须与molecule同一虚拟环境否则molecule verify找不到验证器。第二步初始化 Molecule 场景cd ansible-role-nginx molecule init scenario --role-name nginx --scenario-name default此命令会自动生成molecule/default/目录含molecule.yml,playbook.ymlmolecule/default/tests/目录含空的test_default.pymolecule/default/verify.yml空 playbook供自定义验证第三步修正molecule.yml配置将自动生成的molecule/default/molecule.yml替换为前文所述的 Ubuntu 18.04 专用配置。特别注意platforms.image必须设为geerlingguy/docker-ubuntu1804-ansible:latest且ansible_python_interpreter必须为/usr/bin/python3。第四步编写第一个测试用例编辑molecule/default/tests/test_default.py填入前文test_nginx_package示例。保存后执行molecule test你会看到清晰的输出-- Test matrix └── default ├── dependency ├── cleanup ├── destroy ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── cleanup └── destroyMolecule 默认执行 12 个阶段。其中idempotence是亮点它会第二次运行converge.yml验证角色幂等性即重复执行不改变系统状态。若你的tasks/main.yml中有apt: namenginx statelatest第二次运行时apt模块会报告changed0Molecule 认为幂等成功若报告changed1则测试失败——这能揪出copy模块未加force: no导致文件被反复覆盖的 bug。4.2 配置 Travis CI.travis.yml的精简与健壮在仓库根目录创建.travis.yml内容如下已优化缓存与超时--- language: python python: 3.6 dist: bionic sudo: required services: - docker # 缓存 pip 包加速后续构建 cache: pip: true directories: - $HOME/.cache/pip # 安装 Molecule 依赖 install: - pip install --upgrade pip - pip install molecule[docker] testinfra # Travis 构建前准备确保 Docker 服务就绪 before_script: - sudo service docker start - docker info # 核心测试命令 script: - molecule test # 构建成功后可选推送测试报告如需集成 SonarQube # after_success: # - echo Test passed. Uploading coverage... # - pip install codecov # - codecov # 清理确保容器被销毁释放磁盘 after_script: - molecule destroy --scenario-name default || true关键点解析services: dockerTravis 会自动启动 Docker daemon但需before_script显式sudo service docker start并docker info验证否则molecule create会卡住。cache: pip: trueTravis 会缓存$HOME/.cache/pip目录下次构建时pip install直接从缓存读取节省 3–4 分钟。molecule destroy --scenario-name default || true|| true是关键。即使destroy失败如容器已被删除也不让整个构建失败保证构建机状态清洁。实操心得首次推送.travis.yml到 GitHub 后Travis 构建日志中若出现ERROR: Could not find a version that satisfies the requirement molecule[docker]大概率是 pip 缓存损坏。此时在 Travis UI 中点击 “Restart build” 并勾选 “Clear cache”即可解决。这是 Travis 的经典坑几乎每个团队都会踩一次。4.3 本地快速验证绕过 Travis用molecule test模拟 CI在本地开发时不必每次git push都等 Travis。你可以用一条命令完全模拟 CI 环境# 1. 清理所有残留容器和镜像避免端口冲突 docker system prune -a -f # 2. 进入角色目录激活虚拟环境 cd ansible-role-nginx source ~/.molecule-env/bin/activate # 3. 强制重新拉取 Ubuntu 18.04 镜像确保最新 docker pull geerlingguy/docker-ubuntu1804-ansible:latest # 4. 运行完整测试等同于 Travis 中的 molecule test molecule test --all # 5. 若只想快速验证 converge 阶段调试角色逻辑 molecule converge # 6. 若 converge 失败进入容器手动调试 molecule login # 在容器内可执行ansible-playbook /tmp/molecule/nginx/converge.yml -vvvmolecule login是神技。它会自动 SSH 进入正在运行的测试容器让你像登录一台真实 Ubuntu 18.04 服务器一样查看/var/log/apt/history.log、journalctl -u nginx、ls -l /etc/nginx/。这比看 Travis 日志快十倍。我习惯在tasks/main.yml中加一句debug: varansible_distribution_release然后molecule converge后molecule login查看变量值确保bionic被正确识别。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”5.1 问题速查表高频故障与一招解决故障现象根本原因解决方案修复耗时ERROR: Failed to create instances. docker.errors.DockerException: Error while fetching server API versionTravis 构建机 Docker daemon 未启动或权限不足在.travis.yml的before_script中添加sudo service docker start docker info2 分钟ERROR: Command docker exec ... returned non-zero exit status 126容器内缺少bash或sh解释器常见于精简镜像改用geerlingguy/docker-ubuntu1804-ansible镜像它预装bash5 分钟重拉镜像test_nginx_package FAILED: AssertionError: assert Falsehost.package(nginx)在容器内找不到包因apt update未执行在角色tasks/main.yml开头添加apt: update_cacheyes或在molecule/default/playbook.yml的pre_tasks中加入3 分钟ERROR: verifier testinfra not foundtestinfra未安装在 Molecule 虚拟环境中pip install testinfra确认which testinfra指向虚拟环境路径1 分钟ERROR: The conditional check ansible_distribution_release bionic failed角色中硬编码判断bionic但 Molecule 容器内ansible_distribution_release返回None在molecule.yml的platforms下添加environment字段注入ANSIBLE_FORCE_COLOR1或改用ansible_facts[distribution_release]10 分钟需查 Ansible 文档5.2 独家避坑技巧提升测试稳定性的 3 个硬核实践技巧一用molecule converge --idempotent替代molecule test做日常调试molecule test默认执行idempotence阶段它会运行两次converge.yml。但第一次converge可能因网络问题失败如apt update超时导致整个测试中断。而molecule converge --idempotent会先运行一次converge再立即运行第二次跳过create、verify等耗时步骤。我日常开发时90% 的时间只用这条命令它能在 40 秒内告诉你“角色逻辑是否幂等”比完整测试快 5 倍。技巧二为apt模块添加超时与重试避免 Travis 网络抖动导致失败Ubuntu 18.04 的apt源有时响应慢。在角色tasks/main.yml中不要写- name: Install nginx apt: name: nginx state: present而应写- name: Install nginx with retry apt: name: nginx state: present cache_valid_time: 3600 register: apt_result until: apt_result is succeeded retries: 3 delay: 10cache_valid_time: 3600表示apt update元数据缓存 1 小时避免每次converge都apt updateuntil/retries/delay实现指数退避重试彻底解决 Travis 构建机网络不稳定问题。技巧三用molecule list和molecule status掌握测试现场状态在 Travis 构建失败时日志只显示最后几行。此时你可以在本地复现# 查看所有场景状态 molecule list # 输出 # Scenario Driver Name Provisioner Name Verifier Name Status Instances # default docker ansible testinfra idle 0 # 查看详细状态包括容器 ID、IP、状态 molecule status # 输出 # default docker ansible testinfra created instance # instance docker ansible testinfra running 172.17.0.2若Status为created说明create成功但converge失败可立即molecule login进入容器若为idle说明容器已被销毁需molecule create重建。这个命令比翻 Travis 日志快 10 倍。5.3 性能优化将单次molecule test从 8 分钟压到 1分50秒我们团队实测的优化组合基于 Ubuntu 18.04 TravisDocker 镜像层缓存在.travis.yml中install阶段前加before_install: - docker pull geerlingguy/docker-ubuntu1804-ansible:latest || true利用 Travis 的 Docker 层缓存避免每次molecule create都拉镜像。Molecule 并行测试若角色有多个平台如ubuntu1804、centos7用molecule test --all会串行执行。改为molecule test --scenario-name ubuntu1804 molecule test --scenario-name centos7 wait利用 Travis 的双核 CPU 并行总耗时减少 40%。禁用非必要验证器molecule test默认运行idempotence、side_effect。若你的角色不修改全局状态如不 touch/etc/hosts可在molecule.yml中禁用scenarios: - name: default test_sequence: - dependency - cleanup - destroy - syntax - create - prepare - converge - verify - cleanup - destroy移除idempotence和side_effect节省 1 分钟。最终一个包含 5 个testinfra断言的nginx角色在优化后molecule test平均耗时 1分48秒标准差小于 5 秒完全满足 CI 的快速反馈需求。6. 后续演进与现实考量当 Ubuntu 18.04 EOL 后这套方案何去何从Ubuntu 18.04 的标准支持已于 2023 年 4 月结束扩展安全维护ESM将持续到 2028 年。这意味着你今天在 Travis CI 上跑的bionic构建明年依然能用但新项目不应再以它为唯一目标。那么这套 Molecule CI 的测试范式该如何平滑演进我的建议是“三步走”第一步保持向后兼容新增focal20.04场景不删除bionic而在molecule.yml中扩展platformsplatforms: - name: ubuntu1804 image: geerlingguy/docker-ubuntu1804-ansible:latest - name: ubuntu2004 image: geerlingguy/docker-ubuntu2004-ansible:latest同时tests/test_default.py中的断言需适配双版本例如def test_nginx_version(host): pkg host.package(nginx) if host.system_info.distribution ubuntu and host.system_info.release 18.04: assert pkg.version.startswith(1.14.) elif host.system_info.distribution ubuntu and host.system_info.release 20.04: assert pkg.version.startswith(1.18.)这样molecule test --all会自动在两个环境运行确保角色行为一致性。第二步将 Travis CI 迁移至 GitHub Actions但复用全部 Molecule 配置GitHub Actions 的ubuntu-20.04runner 已非常成熟。.github/workflows/ci.yml可精简为name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-20.04 steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.8 - name: Install dependencies run: pip install molecule[docker] testinfra - name: Run Molecule test run: molecule test你会发现除了 runner 名称和 Python 版本其余molecule.yml、test_default.py、converge.yml全部无需修改。Molecule 的抽象能力正是它历经 CI 平台变迁而屹立不倒的原因。第三步引入molecule lint和ansible-lint让测试前移在script阶段加入script: - ansible-lint - molecule lintansible-lint检查 YAML 语法、最佳实践如禁止command模块代替aptmolecule lint检查molecule.yml格式。它们在converge之前执行能在 10 秒内发现 80% 的低级错误避免浪费 2 分钟去拉容器。最后分享一个个人体会我坚持为每个 Ansible 角色写 Molecule 测试已五年累计覆盖 127 个角色。最大的收益不是“减少了线上故障”而是“改变了团队的交付心智”。当新人提交 PR 时第一反应不再是“我本地测试过了”而是“CI 是否绿了”。当角色需要升级 Nginx 版本时我们