把 Docker 用作应用交付单元、用 Ansible 完成多主机调度与配置同步,是许多中小团队落地自动化的务实选择。这种方式既能利用容器消除环境差异,又不需要引入复杂的编排平台。下面从工程实践角度拆解两者的协作方式。

一、控制节点与受控环境准备
在开始集成之前,需要保证 Ansible 控制机能够通过 SSH 免密登录到所有目标主机,并且这些主机已经安装好 Docker 引擎。Ansible 本身不依赖被管节点上的代理程序,只要求 Python 环境可用,而 Docker 则提供了统一的运行时。两者结合时,控制机负责“说什么”,目标机的 Docker 负责“做什么”。
建议将构建镜像的机器和最终运行容器的机器在 inventory 文件中分开分组。构建机只需要 Docker 与构建上下文,运行机则需要暴露端口并挂载数据卷。这样能避免把庞大的构建过程放到生产机执行。以下示例展示了最简化的主机清单写法:
[builders] build-node-01 ansible_host=192.168.0.10 [runners] run-node-01 ansible_host=192.168.0.11 run-node-02 ansible_host=192.168.0.12 [all:vars] ansible_python_interpreter=/usr/bin/python3
分开分组后,可以在 playbook 中通过 hosts 指定不同任务的作用范围。例如镜像构建只在 builders 组运行,容器启动只在 runners 组运行。这种结构让部署逻辑清晰,也方便后续横向扩容时直接往 runners 组加机器。
二、用 Ansible Role 封装镜像构建
将 Docker 镜像构建过程写成 Ansible Role,可以复用变量与模板,避免每次手动执行 docker build。Role 中通常使用 docker_image 模块,配合本地 Dockerfile 与构建参数完成打包。把版本号、基础镜像名等提取为变量,能让同一套角色支持多环境构建。
下面是一个简化版的构建任务示例,它把业务代码拷贝进构建目录并打上语义化标签:
- name: 拷贝构建上下文
copy:
src: ./app_code
dest: /tmp/build/app_code
- name: 构建后端镜像
docker_image:
name: ipipp.com/backend
tag: "{{ app_version }}"
build:
path: /tmp/build
dockerfile: Dockerfile
source: build
force_source: true
使用 force_source 可以确保每次版本变更都重新构建,而不会命中本地旧缓存。在 CI 环境中,这个 Role 可以由流水线调用;在纯 Ansible 场景下,也能通过命令行传入额外变量完成构建。相比手写 shell 脚本,Role 自带幂等性,重复执行不会生成多余镜像层。
需要注意,构建机本身不一定要长期保留镜像。可以通过 docker_image 的 push 参数把镜像推送到私有仓库,然后让运行机以拉取方式启动,从而减少构建机磁盘压力。私有仓库地址建议用变量注入,不要硬编码在 Role 内。
三、通过 Playbook 驱动容器生命周期
Ansible 的 docker_container 模块是集成部署的核心。它支持声明式地描述容器状态:镜像、端口、环境变量、重启策略、挂载卷等。当 playbook 再次运行时,模块会对比实际状态与期望状态,仅做必要变更。这种机制比直接调 docker run 更安全。
以下片段展示如何在 runners 组上启动一个带健康检查与自动重启的 Web 容器:
- hosts: runners
tasks:
- name: 启动后端容器
docker_container:
name: backend
image: "ipipp.com/backend:{{ app_version }}"
state: started
restart_policy: unless-stopped
ports:
- "8080:8080"
env:
ENV_NAME: production
LOG_LEVEL: info
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
上例中,ports 把容器 8080 映射到主机,env 注入运行参数,healthcheck 让 Docker 自行探测存活。若某台 runner 上已存在同名容器但镜像版本不同,Ansible 会先停止旧容器再启动新容器,实现滚动替换的基础能力。
对于多容器组合,可在同一 playbook 中按顺序启动数据库、缓存与业务服务,并用 wait_for 模块确认依赖端口就绪。虽然这不如 Kubernetes 的编排完备,但对于十台以内节点已足够稳定。同时所有操作记录都在 playbook 与执行输出中,方便审计与回滚。
四、凭据管理与常见误区
在 Docker 与 Ansible 集成时,最常见的误区是把数据库密码、TLS 证书直接写进 playbook 或 Dockerfile。正确做法是用 Ansible Vault 加密变量文件,或者在运行机通过 docker secret 挂载。Vault 文件在版本库中也是密文,只在执行时由控制机解密。
另一个误区是让 Ansible 直接 ssh 进容器做配置。容器应当被视为不可变单元,配置应通过环境变量或挂载卷在启动时给定,而不是启动后再改。若确实要初始化数据,应写在容器入口脚本或一次性 sidecar 任务中,而不是用 Ansible 登录容器内部执行命令。
集成部署的目标不是工具堆叠,而是让“构建一次、到处运行”真正可控。Docker 管交付物,Ansible 管交付动作,边界清晰才容易维护。
当团队规模扩大、容器数量超过单机承受范围时,再考虑将 Ansible 生成的镜像接入更重的编排系统也不迟。在此之前,用本文所述方式已经能覆盖绝大多数中小项目的发布需求。