一、项目整体架构
| 角色 | 主机名 | IP | 职责 |
|---|---|---|---|
| Ansible 控制节点 | ansible-controller | 192.168.24.100 | 下发命令、管理所有节点 |
| 被管理节点 1 | docker-node1 | 192.168.24.101 | Docker + Nginx + MySQL + Redis |
| 被管理节点 2 | docker-node2 | 192.168.24.102 | Docker + Nginx + MySQL + Redis |
系统:RHEL 9.3 网络::桥接模式 安全防火墙开启、SELinux 开启
注意:ansible中文件书写格式缩进要格外注意。
二、项目完整流程
- 初始化 3 台虚拟机(IP、防火墙、SELinux、主机名)
- 配置控制节点 SSH 免密登录
- 控制节点安装 Ansible
- 创建 Ansible 项目目录结构
- 编写主机清单、模板文件
- 编写 3 个核心 Role
- docker_base:安装 Docker
- docker_apps:部署 Nginx+MySQL+Redis
- docker_ops:容器运维
- 编写 4 个 Playbook
- init.yml:初始化 Docker
- deploy.yml:部署应用
- ops.yml:日常运维
- update.yml:无停机升级
- 执行并验证
三、第 1 阶段:3 台虚拟机通用初始化(每台都执行)
1. 设置静态 IP
物理网卡名字设自己的
控制节点(192.168.24.100)
nmcli connection modify eth0 \
ipv4.addresses 192.168.24.100/24 \ # 设置静态IP
ipv4.gateway 192.168.24.2 \ # 网关
ipv4.dns 8.8.8.8 \ # DNS
ipv4.method manual \ # 静态模式
autoconnect yes # 开机自启
nmcli connection up eth0 # 生效
node1(192.168.24.101)
nmcli connection modify eth0 \
ipv4.addresses 192.168.24.101/24 \
ipv4.gateway 192.168.24.2 \
ipv4.dns 8.8.8.8 \
ipv4.method manual \
autoconnect yes
nmcli connection up eth0
node2(192.168.24.102)
nmcli connection modify eth0 \
ipv4.addresses 192.168.24.102/24 \
ipv4.gateway 192.168.24.2 \
ipv4.dns 8.8.8.8 \
ipv4.method manual \
autoconnect yes
nmcli connection up eth0
可以通过脚本快捷的去实现
#!/bin/bash
# useage: sudo ./init_sys.sh <hostname> <ip_address> [gateway] [dns]
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "此脚本必须以root权限运行"
exit 1
fi
}
usage() {
echo "用法: $0 <hostname> <ip_address> [gateway] [dns_servers]"
echo ""
echo "参数说明:"
echo " hostname 要设置的主机名"
echo " ip_address 要设置的静态IP地址 (如: 192.168.72.100)"
echo " gateway 网关地址 (如: 192.168.72.2)"
echo " dns_servers DNS服务器,逗号分隔 (可选,默认: 8.8.8.8,223.5.5.5)"
echo ""
echo "示例:"
echo " $0 myserver 192.168.72.100 192.168.72.2 8.8.8.8,223.5.5.5"
echo " $0 webserver 10.0.0.50"
exit 1
}
get_interface() {
# 尝试获取第一个活动的非环回接口
local interface=$(ip route | grep default | head -1 | awk '{print $5}' 2>/dev/null)
if [[ -z "$interface" ]]; then
# 如果没有默认路由,获取第一个非环回接口
interface=$(ip link show | grep -v lo | grep 'state UP' | head -1 | awk -F': ' '{print $2}')
fi
if [[ -z "$interface" ]]; then
log_error "无法自动检测网络接口"
read -p "请输入网络接口名称 (如: eth0, ens160): " interface
fi
echo "$interface"
}
set_hostname() {
local hostname=$1
log_info "正在设置主机名为: $hostname"
/usr/bin/hostnamectl set-hostname $hostname
log_info "主机名设置完成"
}
close_selinux_firewalld() {
log_info "关闭selinux"
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=permissive/" /etc/selinux/config
log_info "关闭防火墙"
/usr/bin/systemctl disable --now firewalld
}
set_static_ip() {
local interface=$(get_interface)
local ip=$1
local gateway=${2:-"192.168.24.2"}
local dns=${3:-"223.5.5.5,8.8.8.8"}
log_info "正在为接口 $interface 配置静态IP: $ip"
nmcli c modify $interface ipv4.method manual ipv4.addresses $ip/24 ipv4.gateway $gateway ipv4.dns $dns connection.autoconnect yes
nmcli c up $interface
}
main() {
if [[ $# -lt 2 ]]; then
usage
fi
check_root
set_hostname "$1"
close_selinux_firewalld
set_static_ip "$2" "$3" "$4"
}
main "$@"
不过以上脚本有关闭防火墙selinux的步骤,可以自己打开,也可以从脚本中删除
2. 防火墙配置(开启,不放行全部)
firewall-cmd --permanent --add-port=22/tcp # 永久放行SSH
firewall-cmd --reload # 重载生效
firewall-cmd --list-ports # 查看放行端口
Ansible 是通过SSH 22 端口远程管理节点,不放行 22 → Ansible 连不上虚拟机!
3. SELinux 配置(保持开启)
semanage fcontext -a -t container_file_t '/data/docker(/.*)?'
semanage fcontext -a -t mysqld_db_t '/data/mysql(/.*)?'
restorecon -Rv /data
-R
大写 R = Recursive(递归)
意思:
不仅改 /data 目录,还要改里面所有文件夹、所有文件
不加 -R 只会改 /data 本身,里面文件不变。
-v
小写 v = verbose(详细输出)
意思:
把修改了哪些文件、改成什么标签,全部打印出来给你看,有这个参数成功后才会出现提示
这三行是给 RHEL 里的 SELinux 安全系统 "报备权限":
告诉系统:
/data/docker 是 Docker 专用目录
/data/mysql 是 MySQL 专用目录
允许它们读写,不要拦截!
至于标签,都是对应官方规定的安全标签
出现这个说明成功了
Relabeled /data/docker from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:container_file_t:s0
Relabeled /data/mysql from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:mysqld_db_t:s0
4. 设置主机名
# 控制节点
hostnamectl set-hostname ansible-controller
# node1
hostnamectl set-hostname docker-node1
# node2
hostnamectl set-hostname docker-node2
5. 安装基础工具
dnf install -y vim wget net-tools
四、第 2 阶段:被管理节点额外配置(101、102 执行)
当然也可以写剧本让其放行端口(这里写的53端口,举例的)
bash
- name: add port for firewall
hosts: docker_nodes
become: yes
tasks:
- name: start and enable firewalld
service:
name: firewalld
state: started
enabled: yes
- name: add 53 port
firewalld:
port: 53/tcp
permanent: yes
immediate: yes
state: enabled
- name: add 53 port
firewalld:
port: 53/udp
permanent: yes
immediate: yes
state: enabled
或者手动放行 Nginx 80 端口:
bash
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload
firewall-cmd --list-ports
不放行 80 → 浏览器访问 192.168.24.101 打不开 Nginx
五、第 3 阶段:控制节点配置(仅 100 执行)
1. 安装 Ansible
本地仓库没有,可以在阿里云镜像源网站中下载epel仓库,再按照,以前的Ansible文章有
dnf install -y ansible
ansible --version
运行ansible还需要python环境,被管理节点也需要
ansible工作流程
控制节点(Python) → 通过 SSH 把脚本发给 → 被管理节点
被管理节点(Python) → 执行脚本 → 返回结果
2. SSH 免密登录
ssh-keygen -t rsa # 一路回车
ssh-copy-id root@192.168.24.101
ssh-copy-id root@192.168.24.102
六、第 4 阶段:创建项目目录(仅 100)
cd /root
mkdir -p ansible-docker-project
cd ansible-docker-project
mkdir -p inventory templates roles playbooks
[root@ansible ansible-docker-project]# tree
.
├── inventory
├── playbooks
├── roles
└── templates
4 directories, 0 files
七、第 5 阶段:所有配置文件 + 逐行解析
文件 1:主机清单 inventory/hosts
bash
[docker_nodes]
192.168.24.101 ansible_user=root
192.168.24.102 ansible_user=root
[docker_nodes:vars]
docker_registry_mirror=https://docker.mirrors.ustc.edu.cn
举例解释:
192.168.24.101 = 被管理节点 1 的 IP
ansible_user=root = Ansible 远程登录这台机器用 root 用户
[docker_nodes:vars]
docker_registry_mirror=https://docker.mirrors.ustc.edu.cn
组变量:下面的变量 给 docker_nodes 组里所有机器共用
docker_registry_mirror
变量名 → Docker 镜像加速地址
https://docker.mirrors.ustc.edu.cn
这是 中国科学技术大学(USTC)的 Docker 官方镜像加速地址
解析:
[docker_nodes]:定义被管理节点组ansible_user=root:用 root 登录docker_registry_mirror:Docker 镜像加速
文件 2:Docker 配置模板 templates/daemon.json.j2
相当于配置镜像加速
bash
{
"default-ipc-mode": "shareable",
"data-root": "/data/docker",
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "50"
},
"registry-mirrors": [
"https://d8jozvml.mirror.aliyuncs.com",
"https://docker.1ms.run",
"https://func.ink",
"https://proxy.1panel.live",
"https://docker-0.unsee.tech",
"https://docker.zhai.cm",
"https://a.ussh.net",
"https://docker.melikeme.cn",
"https://lispy.org",
"https://docker.hlmirror.com",
"https://docker.xiaogenban1993.com",
"https://docker.1panel.top",
"https://docker.kejilion.pro",
"https://dockerpull.cn",
"https://docker.xuanyuan.me",
"https://docker.anye.in",
"https://hub.fast360.xyz"
]
}
语法补充:
[] = JSON 数组格式
Docker 要求 registry-mirrors 必须是数组
所以哪怕只有一个加速地址,也要用 [] 包起来
{{ 变量 }} 是 Ansible 自动填值的语法
解析:
- 镜像加速
- 日志切割,防止占满磁盘
- Docker 数据存到 /data/docker
文件 3:应用编排模板 templates/docker-compose.yml.j2
后缀名解释:
.yml 表示这是 YAML 格式的配置文件
.j2 表示这是 Jinja2 模板文件(Ansible 用它来自动填变量)
合起来就是:
这是一个【Ansible 能自动填变量】的 YAML 配置模板
作用:
通过一个配置文件,一键定义、下载、启动 Nginx + MySQL + Redis 三个容器,并且设置好启动顺序、数据持久化、开机自启、端口映射。
bash
# 指定 Docker Compose 文件的语法版本,3.8 是稳定通用版
version: '3.8'
# 定义所有需要运行的容器服务(mysql、redis、nginx)
services:
# 服务 1:MySQL 数据库
mysql:
# 指定使用的镜像:mysql 8.0 版本,本地没有会自动下载
image: mysql:8.0
# 给容器起名字:mysql,方便管理
container_name: mysql
# 开机自启 + 容器崩溃自动重启(生产环境必须加)
restart: always
# 环境变量:给 MySQL 传递配置参数(自动初始化)
environment:
# 设置 MySQL root 用户密码
MYSQL_ROOT_PASSWORD: Root@123456
# 容器启动时,自动创建名为 ansible_docker 的数据库
MYSQL_DATABASE: ansible_docker
# 数据卷挂载:宿主机目录:容器内目录
# 作用:把数据库数据保存到宿主机 /data/mysql,容器删除数据不丢失
volumes:
- /data/mysql:/var/lib/mysql
# 健康检查:判断 MySQL 是否真正启动成功
healthcheck:
# 每 10 秒执行一次 mysqladmin ping 检查 MySQL 是否存活,docker-compose 健康检查要求必须用 JSON 数组格式
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
# 服务 2:Redis 缓存数据库
redis:
# 使用 redis 7 版本镜像
image: redis:7
# 容器名字叫 redis
container_name: redis
# 开机/崩溃自动重启
restart: always
# 依赖关系:必须等 mysql 健康检查成功(真正启动完成)后,再启动 redis
depends_on:
mysql: { condition: service_healthy }
# 服务 3:Nginx 网页服务
nginx:
# 使用 nginx 1.25 版本镜像
image: nginx:1.25
# 容器名字叫 nginx
container_name: nginx
# 开机/崩溃自动重启
restart: always
# 端口映射:宿主机80端口 → 容器80端口
# 作用:浏览器访问宿主机IP就能打开 Nginx 页面
ports:
- "80:80"
# 依赖关系:等 redis 启动完成后,再启动 nginx
depends_on:
- redis
逐行解析
image: mysql:8.0→ Docker 会自动下载这个镜像volumes→ 数据持久化healthcheck→ 健康检查depends_on→ 启动顺序:MySQL → Redis → Nginxports: 80:80→ 对外提供网页访问
八、第 6 阶段:3 个核心 Role(逐行解析)
Role 1:docker_base 安装 Docker
以下为路径:
[root@ansible tasks]# pwd
/root/ansible-docker-project/roles/docker-base/tasks
[root@ansible tasks]# vim main.yml
[root@ansible tasks]# ll
total 0
-rw-r--r--. 1 root root 0 Apr 22 19:09 main.yml
- name: 安装依赖工具
dnf:
name: [curl, yum-utils]
state: present
1. - name: 安装依赖工具
-:表示这是 一个任务(task)
name::给任务起个名字(方便你看日志)
2. dnf:
这是 Ansible 模块 → 等于 Linux 的 dnf install
作用:
在 RHEL / CentOS 系统里安装软件包
相当于 Linux 命令:
dnf install -y 软件名
3. name: [curl, yum-utils]
name::指定要安装的包名
[curl, yum-utils]:一次性安装 两个工具
这两个是干什么的?
curl:下载工具
yum-utils:管理软件源的工具(安装 Docker 必须用它)
4. state: present
超级关键!Ansible 固定写法
意思:
确保这个软件 已经安装
如果没装 → 自动安装
如果已经装了 → 不动
→ 安装系统依赖
- name: 添加Docker源
command: dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
没科学上网的话用国内镜像源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
→ 添加官方 Docker 源
- name: 安装Docker引擎
dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
→ 安装 Docker 和 Compose
bash
- name: 写入Docker配置
# 使用 Ansible 的 template 模板模块(专门渲染变量+传文件)
template:
# 来源文件:从本机 templates 目录下取这个模板
src: ../../templates/daemon.json.j2 #要写绝对路径,不然找不到
# 目标路径:把文件传到被管理机器的这个位置
dest: /etc/docker/daemon.json
→ 渲染配置文件
- name: 启动Docker
systemd:
name: docker
state: restarted
enabled: yes
→ 启动并开机自启
Role 2:docker_apps 部署应用栈
路径如下:
[root@ansible tasks]# pwd
/root/ansible-docker-project/roles/docker-apps/tasks
- name: 创建应用目录
file:
path: /opt/docker-apps
state: directory
→ 创建目录
- name: 渲染docker-compose.yml
template:
src: docker-compose.yml.j2
dest: /opt/docker-apps/docker-compose.yml
→ 把模板传到节点
# 任务名称:启动 Nginx / MySQL / Redis 所有容器
- name: 启动所有容器
# 使用 command 模块,执行 Linux 原生命令,-d:detached(后台运行)
command: docker compose up -d
# 额外参数:指定在哪个目录下执行这条命令
args:
chdir: /opt/docker-apps
【重点】这里自动做了什么?
- 检查本地是否有镜像
- 没有 → 自动下载 Nginx、MySQL、Redis
- 按顺序启动容器
Role 3:docker_ops 运维
路径如下:
bash
[root@ansible tasks]# pwd
/root/ansible-docker-project/roles/docker_ops/tasks
bash
- name: create require directory
file:
path: /var/log/docker
state: directory
# 任务名称:批量导出容器日志到文件
- name: 导出容器日志
# 执行 Linux 命令:导出容器日志
shell: docker logs {{ item }} > /var/log/docker/{{ item }}.log
# 循环遍历:依次把 nginx、mysql、redis 代入上面的 {{ item }}
loop: [nginx, mysql, redis]
→ 批量导出日志
bash
- name: 清理无用镜像
command: docker image prune -f # 强制Docker 清理悬空 / 无用镜像 的命令,核心作用是一键删除没用的镜像,释放磁盘空间。
→ 释放磁盘空间
bash
- name: 查看容器状态
command: docker ps
→ 显示运行状态
九、第 7 阶段:4 个 Playbook 完整解析
路径全部写在playbooks下
1. init.yml 安装 Docker
bash
- name: 批量安装Docker
hosts: docker_nodes
roles:
- docker_base
→ 对所有节点执行 docker_base 角色
2. deploy.yml 部署应用
bash
- name: 部署Nginx+MySQL+Redis
hosts: docker_nodes
roles:
- docker_apps
→ 执行应用部署
3. ops.yml 运维
bash
- name: 容器日志、清理、监控
hosts: docker_nodes
roles:
- docker_ops
4. update.yml 无停机升级
bash
- name: 无停机更新Nginx(真正生产可用版)
hosts: docker_nodes
tasks:
- name: 推送最新 docker-compose.yml 到节点
template:
src: ../templates/docker-compose.yml.j2
dest: /opt/docker-apps/docker-compose.yml
# 2. 拉取新版本镜像
- name: 拉取最新Nginx镜像
command: docker compose pull nginx
args:
chdir: /opt/docker-apps
# 3. 只更新Nginx,不影响数据库(真正无停机)
- name: 无停机重启 Nginx
command: docker compose up -d --no-deps nginx
args:
chdir: /opt/docker-apps
逐行解析
docker compose pull nginx→ 下载最新 Nginx 镜像--no-deps→ 只更新 Nginx,不重启 MySQL、Redisup -d→ 自动替换旧容器,秒级完成
十、第 8 阶段:完整执行流程(最终步骤:以下步骤可能会比较慢,拉取镜像下载软件都是看网速的)
注意:指定清单主机要用-i这个参数,或者文件要设置为ini的后缀,不然要被ansible解析为yml格式
开始之前要知道Ansible 默认只会在当前目录(./roles)找角色,所以要配置ansible.cfg文件
bash
[defaults]
inventory = inventory/hosts
roles_path = ./roles
host_key_checking = false
也可以自动生成一个cfg文件
# 生成全注释的基础版(推荐)
ansible-config init --disabled > ansible.cfg
# 生成包含所有插件的完整版
ansible-config init --disabled -t all > ansible.cfg
1. 安装 Docker
ansible-playbook -i inventory/hosts playbooks/init.yml
执行成功反馈
bash
[root@ansible ansible-docker-project]# ansible-playbook -i inventory/hosts playbooks/init.yml
PLAY [batch installation Docker] ***********************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.24.101]
ok: [192.168.24.102]
TASK [docker_base : install depend-on tools] ***********************************
ok: [192.168.24.102]
ok: [192.168.24.101]
TASK [docker_base : add docker source] *****************************************
changed: [192.168.24.101]
changed: [192.168.24.102]
TASK [docker_base : install docker engine] *************************************
ok: [192.168.24.102]
ok: [192.168.24.101]
TASK [docker_base : write the docker configuration] ****************************
changed: [192.168.24.101]
changed: [192.168.24.102]
TASK [docker_base : start docker] **********************************************
changed: [192.168.24.101]
changed: [192.168.24.102]
PLAY RECAP *********************************************************************
192.168.24.101 : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.24.102 : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@ansible ansible-docker-project]#
验证:
bash
[root@docker-node1 ~]# systemctl is-active docker.service
active
2. 部署应用(自动下载镜像)
报错:
中科大加速源(docker.mirrors.ustc.edu.cn)
bash
dial tcp: lookup docker.mirrors.ustc.edu.cn on 223.5.5.5:53: no such host
如果镜像源都失效可能报以上错误,你修改镜像加速文件后要重新执行init.yml,因为是在这里去加载镜像加速的
ansible-playbook -i inventory/hosts playbooks/deploy.yml
执行成功反馈
bash
[root@ansible ansible-docker-project]# ansible-playbook -i inventory/hosts playbooks/deploy.yml
PLAY [deploy nginx+mysql+redis] ************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.24.101]
ok: [192.168.24.102]
TASK [docker_apps : create app directory] **************************************
ok: [192.168.24.102]
ok: [192.168.24.101]
TASK [docker_apps : render docker-compose.yml] *********************************
ok: [192.168.24.102]
ok: [192.168.24.101]
TASK [docker_apps : start all containers] **************************************
changed: [192.168.24.102]
changed: [192.168.24.101]
PLAY RECAP *********************************************************************
192.168.24.101 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.24.102 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@ansible ansible-docker-project]#
验证:
bash
[root@docker-node1 ~]# docker images
i Info → U In Use
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
mysql:8.0 d0304ed9fdb6 1.1GB 249MB U
nginx:1.25 a484819eb602 273MB 73.9MB U
redis:7 c9452281a467 173MB 47.5MB U
[root@docker-node1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fd1a2afe3b0 nginx:1.25 "/docker-entrypoint...." 12 minutes ago Up 12 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp nginx
08ec132c51b2 redis:7 "docker-entrypoint.s..." 12 minutes ago Up 12 minutes 6379/tcp redis
36f6a2b1e60b mysql:8.0 "docker-entrypoint.s..." 12 minutes ago Up 12 minutes (healthy) 3306/tcp, 33060/tcp mysql
3. 执行运维
ansible-playbook -i inventory/hosts playbooks/ops.yml
command没有>,会报错,shell可以
bash
stderr: "docker: 'docker logs' requires 1 argument"
成功反馈:
bash
[root@ansible ansible-docker-project]# ansible-playbook -i inventory/hosts playbooks/ops.yml
PLAY [容器日志、清理、监控] ****************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.24.102]
ok: [192.168.24.101]
TASK [docker_ops : create require directory] ***********************************
changed: [192.168.24.102]
changed: [192.168.24.101]
TASK [docker_ops : export container logs] **************************************
changed: [192.168.24.102] => (item=nginx)
changed: [192.168.24.101] => (item=nginx)
changed: [192.168.24.102] => (item=mysql)
changed: [192.168.24.101] => (item=mysql)
changed: [192.168.24.102] => (item=redis)
changed: [192.168.24.101] => (item=redis)
TASK [docker_ops : clean up useless images] ************************************
changed: [192.168.24.102]
changed: [192.168.24.101]
TASK [docker_ops : check container status] *************************************
changed: [192.168.24.101]
changed: [192.168.24.102]
PLAY RECAP *********************************************************************
192.168.24.101 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.24.102 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@ansible ansible-docker-project]#
4. 无停机升级
报错:
出现以下报错可能是因为网络确实不佳,成功一个的情况
bash
short read: expected 6783 bytes but got 1363: unexpected EOF
TASK [docker_apps : start all containers] **************************************
fatal: [192.168.24.101]: FAILED! => {"changed": true, "cmd": ["docker", "compose", "up", "-d"], "delta": "0:01:01.021586", "end": "2026-04-22 21:38:38.617716", "msg": "non-zero return code", "rc": 1, "start": "2026-04-22 21:37:37.596130", "stderr": "time=\"2026-04-22T21:37:37+08:00\" level=warning msg=\"/opt/docker-apps/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"\n Image nginx:1.24 Pulling \n 54247e67c145 Download complete 0B\n 2f39813903ff Downloading 1.049MB\n 2f39813903ff Downloading 1.049MB\n 2f39813903ff Downloading 1.049MB\n 2f39813903ff Downloading 2.097MB\n 2f39813903ff Downloading 2.097MB\n 2f39813903ff Downloading 2.097MB\n 2f39813903ff Downloading 2.097MB\n 2f39813903ff Download complete 0B\nshort read: expected 6783 bytes but got 1363: unexpected EOF", "stderr_lines": ["time=\"2026-04-22T21:37:37+08:00\" level=warning msg=\"/opt/docker-apps/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"", " Image nginx:1.24 Pulling ", " 54247e67c145 Download complete 0B", " 2f39813903ff Downloading 1.049MB", " 2f39813903ff Downloading 1.049MB", " 2f39813903ff Downloading 1.049MB", " 2f39813903ff Downloading 2.097MB", " 2f39813903ff Downloading 2.097MB", " 2f39813903ff Downloading 2.097MB", " 2f39813903ff Downloading 2.097MB", " 2f39813903ff Download complete 0B", "short read: expected 6783 bytes but got 1363: unexpected EOF"], "stdout": "", "stdout_lines": []}
changed: [192.168.24.102]
PLAY RECAP *********************************************************************
192.168.24.101 : ok=3 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
192.168.24.102 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
注意:1.如果你本来的版本就是能拉到的最新的,就不会变化,甚至会认为不用切换,根本不会改变,nginx服务的启动时间都没有变化。
2.如果写的是compose命令,必须依靠docker-compose.yml.j2这个文件,所以你写的是什么版本,你启动的镜像就是什么版本。
ansible-playbook -i inventory/hosts playbooks/update.yml
成功反馈:
bash
[root@ansible ansible-docker-project]# ansible-playbook -i inventory/hosts playbooks/update.yml
PLAY [无停机更新Nginx] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.24.101]
ok: [192.168.24.102]
TASK [command] *****************************************************************
changed: [192.168.24.102]
changed: [192.168.24.101]
TASK [command] *****************************************************************
changed: [192.168.24.101]
changed: [192.168.24.102]
PLAY RECAP *********************************************************************
192.168.24.101 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.24.102 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@ansible ansible-docker-project]#
验证:
注意:compose的主要作用是为了一键部署容器,将要启动的容器,拉取的镜像都写到docker-compose.yml中,所以用不然就得一条一条的docker run 去启动,docker compose 这个命令依赖这个文件,用其进行无停机更新,核心参数是--no-deps,只重启指定服务,不影响依赖服务。而且启动的版本就是docker-compose.yml里面写的版本,所以先去改版本,在去执行更新剧本,注意更新剧本中要有更新节点docker-compose.yml文件。
刚开始:
[root@docker-node1 ~]# docker ps | grep -E "mysql|redis"
08ec132c51b2 redis:7 "docker-entrypoint.s..." 15 hours ago Up 32 minutes 6379/tcp redis
36f6a2b1e60b mysql:8.0 "docker-entrypoint.s..." 15 hours ago Up 32 minutes (healthy) 3306/tcp, 33060/tcp mysql
[root@docker-node1 ~]# docker ps | grep nginx
1702349716a1 nginx:1.24 "/docker-entrypoint...." 14 hours ago Up 32 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp nginx
脚本执行后:
[root@docker-node1 ~]# docker ps | grep -E "mysql|redis"
08ec132c51b2 redis:7 "docker-entrypoint.s..." 15 hours ago Up 38 minutes 6379/tcp redis
36f6a2b1e60b mysql:8.0 "docker-entrypoint.s..." 15 hours ago Up 38 minutes (healthy) 3306/tcp, 33060/tcp mysql
[root@docker-node1 ~]# docker ps | grep nginx
5c9a8423516c nginx:1.25 "/docker-entrypoint...." 12 seconds ago Up 11 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp nginx
从以上结果会发现mysql和redis的启动时间没变化,nginx的启动时间刷新。
十一、最终验证
bash
浏览器访问:
http://192.168.24.101
http://192.168.24.102
成功例子:
