前言
本教程使用 Ansible 实现全栈应用的自动化部署,包括:
- 后端:Spring Boot(运行在 Tomcat 或 jar 方式)
- 数据库:MySQL
- 缓存:Redis
- 前端:Vue.js(部署在 Nginx)
- 代理:Nginx(反向代理 Vue 和 Spring Boot)
安装 Ansible
在 Ansible 控制节点(本地机器或 CI/CD 服务器)上安装:(本文以Rockylinux9.6为例)
这里看我的博客文章:Ansible 环境搭建
这里一共两个节点,192.168.92.19:ansible控制节点,192.168.92.20:被控制节点
确保 Ansible 已正确安装,先检查 Ansible 是否安装:
rpm -qa | grep ansible

[root@ansible ~]# ansible --version
ansible [core 2.14.18]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.9.21 (main, Feb 10 2025, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-5)] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True
配置 Ansible Hosts
编辑 /etc/ansible/hosts 添加你的目标服务器:
[webservers]
192.168.92.20
在这里配置完后要用ssh设置密钥,生成密钥,将本地的密钥推送,推送到远程的192.168.92.20机器(被控制端)---这些看我的博客:Ansible 环境搭建
测试连接:
[root@ansible ~]# ansible all -m ping
192.168.92.20 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
这说明 Ansible 已经完全配置成功。
创建 Ansible Playbook
创建目录结构:
# 创建项目根目录并进入
mkdir -p ansible-playbook
cd ansible-playbook
# 为所有角色创建 tasks 目录(每个角色都需要)
mkdir -p roles/{docker_install,system_init,mysql,redis,springboot,nginx,vue}/tasks
# 为需要存放静态文件的角色创建 files 目录
mkdir -p roles/mysql/files
mkdir -p roles/springboot/files
mkdir -p roles/vue/files
mkdir -p roles/docker_install/files # 新增:用于存放预置的 docker-compose 二进制文件
# 为需要存放配置模板的角色创建 templates 目录
mkdir -p roles/springboot/templates
mkdir -p roles/nginx/templates
# 为 docker_install 角色创建 handlers 目录(存放 handler 定义)
mkdir -p roles/docker_install/handlers
项目的树形包结构
[root@ansible ansible-playbook]# tree
.
├── roles
│ ├── docker_install
│ │ ├── handlers
│ │ │ └── main.yml
│ │ └── tasks
│ │ └── main.yml
│ ├── mysql
│ │ ├── files
│ │ │ └── init.sql
│ │ └── tasks
│ │ └── main.yml
│ ├── nginx
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── nginx.conf.j2
│ ├── redis
│ │ └── tasks
│ │ └── main.yml
│ ├── springboot
│ │ ├── files
│ │ │ └── Springbootdemo-0.0.1-SNAPSHOT.jar
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── Dockerfile.j2
│ ├── system_init
│ │ └── tasks
│ │ └── main.yml
│ └── vue
│ ├── files
│ │ └── dist
│ │ ├── assets
│ │ │ ├── 1-DxVeaUtM.jpg
│ │ │ ├── 2-U6Qjex4J.jpg
│ │ │ ├── yun-DW8jof7n.jpg
│ │ │ ├── index-CYqzScuv.js
│ │ │ └── index-Cp89o39-.css
│ │ ├── favicon.ico
│ │ └── index.html
│ └── tasks
│ └── main.yml
└── site.yml
23 directories, 20 files
创建 system_init 角色(基础系统配置)
mkdir -p /root/ansible-playbook/roles/system_init/tasks
vi /root/ansible-playbook/roles/system_init/tasks/main.yml
写入以下内容(与你手工部署步骤对应):
---
# 关闭防火墙
- name: 停止 firewalld 服务
systemd:
name: firewalld
state: stopped
enabled: no
# 关闭 SELinux
- name: 禁用 SELinux(临时)
selinux:
state: disabled
- name: 永久禁用 SELinux(修改配置文件)
lineinfile:
path: /etc/selinux/config
regexp: '^SELINUX='
line: 'SELINUX=disabled'
backup: yes
# 配置 Rocky Linux yum 源为阿里云镜像
- name: 备份原 yum 源并替换为阿里云源
shell: |
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak /etc/yum.repos.d/rocky*.repo
dnf makecache
# 安装基础工具(可选,便于调试)
- name: 安装基础工具包
dnf:
name:
- vim
- wget
- curl
- net-tools
state: present
创建 docker_install 角色(安装 Docker 环境)
安装必要的 Ansible 集合
由于使用了 docker_network、docker_container 等模块,需要安装 community.docker 集合:
ansible-galaxy collection install community.docker
预置 Docker Compose 二进制文件,在控制节点(192.168.92.19)上手动下载 Docker Compose(仅一次),运行的时候开了vpn下载 Docker CE 仓库文件报错,不开下载 Docker Compose 二进制文件报错
# 如果之前下载过,直接使用已有文件;否则执行:
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64" -o /tmp/docker-compose
# 将文件复制到角色的 files 目录
cp /tmp/docker-compose /root/ansible-playbook/roles/docker_install/files/docker-compose-Linux-x86_64
mkdir -p /root/ansible-playbook/roles/docker_install/tasks
vi /root/ansible-playbook/roles/docker_install/tasks/main.yml
写入内容(对应你手工部署中的 Docker 安装步骤):
---
# 移除旧版 Docker
- name: 移除旧版 Docker 组件
dnf:
name:
- docker
- docker-common
- docker-selinux
- docker-engine
state: absent
ignore_errors: yes
# 安装依赖
- name: 安装必要依赖包
dnf:
name:
- yum-utils
- device-mapper-persistent-data
- lvm2
state: present
# 添加 Docker CE 仓库(华为云镜像)
- name: 下载 Docker CE 仓库文件
get_url:
url: https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo
dest: /etc/yum.repos.d/docker-ce.repo
- name: 替换仓库地址为华为云
replace:
path: /etc/yum.repos.d/docker-ce.repo
regexp: 'download.docker.com'
replace: 'mirrors.huaweicloud.com/docker-ce'
# 安装 Docker CE
- name: 安装 Docker CE
dnf:
name: docker-ce
state: present
# 启动并设置开机自启
- name: 启动 Docker 服务并设置开机自启
systemd:
name: docker
state: started
enabled: yes
# 配置镜像加速器
- name: 创建 Docker 配置目录
file:
path: /etc/docker
state: directory
mode: '0755'
- name: 配置镜像加速器
copy:
dest: /etc/docker/daemon.json
content: |
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.m.daocloud.io",
"https://docker.xuanyuan.me"
]
}
notify: restart docker
- name: 安装 pip
dnf:
name: python3-pip
state: present
- name: 安装 Python 依赖(requests 和 docker)
pip:
name:
- requests
- docker
state: present
# 安装 Docker Compose
- name: 复制 Docker Compose 二进制文件
copy:
src: docker-compose-Linux-x86_64
dest: /usr/local/bin/docker-compose
mode: '0755'
# 验证安装
- name: 验证 Docker Compose 安装
command: docker-compose -v
register: compose_version
changed_when: false
failed_when: false
- name: 输出 Docker Compose 版本
debug:
msg: "Docker Compose installed: {{ compose_version.stdout }}"
when: compose_version.rc == 0
# 创建自定义网络 app-network(幂等)
- name: 创建 Docker 网络 app-network
docker_network:
name: app-network
driver: bridge
state: present
在 Ansible 角色中,handlers 必须放在独立的 handlers/main.yml 文件中。
[root@ansible ansible-playbook]# mkdir -p /root/ansible-playbook/roles/docker_install/handlers
#vi /root/ansible-playbook/roles/docker_install/handlers/main.yml
---
- name: restart docker
systemd:
name: docker
state: restarted
daemon_reload: yes
MySQL 自动化部署
MySQL 任务文件(roles/mysql/tasks/main.yml)
# 1. 创建宿主机数据目录
- name: 创建 MySQL 数据目录
file:
path: /data/mysql/data
state: directory
- name: 创建 MySQL 初始化脚本目录
file:
path: /data/mysql/init
state: directory
# 2. 将初始化 SQL 复制到宿主机
- name: 复制 init.sql 到宿主机
copy:
src: init.sql
dest: /data/mysql/init/init.sql
mode: '0644'
# 3. 创建 Docker 网络(网络已在 docker_install 角色中统一创建,此处无需重复)
# 4. 启动 MySQL 容器
- name: 运行 MySQL 容器
docker_container:
name: mysql
image: mysql:8.0
state: started
restart_policy: always
ports:
- "3306:3306"
env:
TZ: "Asia/Shanghai"
LANG: "en_US.UTF-8"
MYSQL_ROOT_PASSWORD: "123456"
MYSQL_DATABASE: "big_event"
volumes:
- "/data/mysql/data:/var/lib/mysql"
- "/data/mysql/init:/docker-entrypoint-initdb.d:ro"
networks:
- name: app-network
command: [
"--default-authentication-plugin=mysql_native_password",
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_general_ci",
"--lower-case-table-names=1",
"--performance-schema=1",
"--skip-log-bin"
]
Redis 自动化部署
Redis 任务文件(roles/redis/tasks/main.yml)
# 1. 创建宿主机数据目录
- name: 创建 Redis 数据目录
file:
path: /data/redis/data
state: directory
mode: '0755'
# 2. 创建 Docker 网络(网络已在 docker_install 角色中统一创建,此处无需重复)
# 3. 启动 Redis 容器
- name: 运行 Redis 容器
docker_container:
name: redis
image: redis:6-alpine
state: started
restart_policy: always
command: redis-server --maxmemory 512mb --appendonly yes
volumes:
- "/data/redis/data:/data"
networks:
- name: app-network
Spring Boot 后端自动化部署
准备文件
- 把
Springbootdemo-0.0.1-SNAPSHOT.jar放到roles/springboot/files/下。 - 把
Dockerfile的模板(Jinja2)放到roles/springboot/templates/Dockerfile.j2,内容就是你手工写的 Dockerfile(可以原样复制,不需要变量也可以直接作为静态文件)。
Spring Boot 任务文件(roles/springboot/tasks/main.yml)
# 1. 创建目标目录(存放构建上下文)
- name: 创建 Spring Boot 构建目录
file:
path: /data/docker-compose/backend
state: directory
mode: '0755'
# 2. 复制 JAR 包
- name: 复制 Spring Boot JAR 包
copy:
src: Springbootdemo-0.0.1-SNAPSHOT.jar
dest: /data/docker-compose/backend/Springbootdemo-0.0.1-SNAPSHOT.jar
mode: '0644'
# 3. 复制 Dockerfile(如果内容固定,也可以直接用 copy 模块)
- name: 复制 Dockerfile
copy:
src: ../templates/Dockerfile.j2
dest: /data/docker-compose/backend/Dockerfile
mode: '0644'
# 或者用 template 模块,如果你需要动态渲染
# 4. 确保自定义网络存在(网络已在 docker_install 角色中统一创建,此处无需重复)
# 5. 构建镜像
- name: 构建 Spring Boot 镜像
docker_image:
name: docker-compose_springboot
build:
path: /data/docker-compose/backend
pull: no
source: build
state: present
# 6. 运行容器
- name: 运行 Spring Boot 容器
docker_container:
name: springboot
image: docker-compose_springboot
state: started
restart_policy: always
env:
SPRING_REDIS_HOST: "redis"
SPRING_REDIS_PORT: "6379"
networks:
- name: app-network
Vue/Nginx 前端自动化部署
Vue 角色中的 synchronize 模块
synchronize 依赖 rsync,如果目标主机未安装,任务可能失败。在ansible控制端(192.168.92.19)执行
ansible webservers -m dnf -a 'name=rsync state=present'
Vue 任务文件(roles/vue/tasks/main.yml)
Vue 只需要把打包好的 dist 文件夹放置到宿主机上,供 Nginx 容器挂载。
# 1. 创建宿主机存放前端静态文件的目录
- name: 创建前端文件目录
file:
path: /data/docker-compose/nginx/dist
state: directory
mode: '0755'
# 2. 将本地 dist 文件夹(位于 roles/vue/files/dist/)复制到目标主机
- name: 复制前端静态文件到宿主机
synchronize:
src: files/dist/
dest: /data/docker-compose/nginx/dist/
delete: yes
# 或者用 copy 模块(如果 dist 不大):
# copy:
# src: files/dist/
# dest: /data/docker-compose/nginx/dist/
Nginx 配置与部署(roles/nginx/tasks/main.yml)
Nginx 使用容器运行,配置文件通过模板生成,并挂载静态文件目录。
# 1. 确保 Nginx 配置文件目录存在(宿主机)
- name: 创建 Nginx 配置目录
file:
path: /data/docker-compose/nginx
state: directory
mode: '0755'
# 2. 使用模板生成 nginx.conf(支持变量,如服务器 IP)
- name: 生成 nginx.conf 配置文件
template:
src: nginx.conf.j2
dest: /data/docker-compose/nginx/nginx.conf
mode: '0644'
# 3. 确保自定义网络存在(网络已在 docker_install 角色中统一创建,此处无需重复)
# 4. 运行 Nginx 容器
- name: 启动 Nginx 容器
docker_container:
name: nginx
image: nginx:alpine
state: started
restart_policy: unless-stopped
ports:
- "80:80"
volumes:
- "/data/docker-compose/nginx/nginx.conf:/etc/nginx/nginx.conf:ro"
- "/data/docker-compose/nginx/dist:/usr/share/nginx/html:ro"
networks:
- name: app-network
Nginx 配置模板(roles/nginx/templates/nginx.conf.j2)
模板内容与你的手工配置完全一致,只是将 server_name 改为变量,方便不同环境修改。
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server {
listen 80;
server_name {{ nginx_server_name | default('192.168.92.20') }};
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
location /api/ {
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://springboot:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 75s;
proxy_read_timeout 300s;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
运行 Ansible Playbook
创建主 Playbook site1.yml
site1.yml 中的 roles 列表会严格按照定义的顺序依次执行。
在 ansible-playbook/ 目录下创建 site1.yml 文件:
- hosts: webservers
become: yes
roles:
- system_init # 系统基础配置
- docker_install # 安装 Docker 环境(含网络创建)
- mysql
- redis
- springboot
- vue
- nginx
cd /root/ansible-playbook
ansible-playbook site1.yml
[root@ansible ansible-playbook]# ansible-playbook site1.yml
[WARNING]: Collection community.docker does not support Ansible version 2.14.18
PLAY [webservers] ***************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************
ok: [192.168.92.20]
TASK [system_init : 停止 firewalld 服务] ****************************************************************************************************
ok: [192.168.92.20]
TASK [system_init : 禁用 SELinux(临时)] ***************************************************************************************************
ok: [192.168.92.20]
TASK [system_init : 永久禁用 SELinux(修改配置文件)] ***************************************************************************************
ok: [192.168.92.20]
TASK [system_init : 备份原 yum 源并替换为阿里云源] ******************************************************************************************
changed: [192.168.92.20]
TASK [system_init : 安装基础工具包] *********************************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 移除旧版 Docker 组件] ************************************************************************************************
changed: [192.168.92.20]
TASK [docker_install : 安装必要依赖包] ******************************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 下载 Docker CE 仓库文件] *********************************************************************************************
changed: [192.168.92.20]
TASK [docker_install : 替换仓库地址为华为云] ************************************************************************************************
changed: [192.168.92.20]
TASK [docker_install : 安装 Docker CE] ******************************************************************************************************
changed: [192.168.92.20]
TASK [docker_install : 启动 Docker 服务并设置开机自启] **************************************************************************************
changed: [192.168.92.20]
TASK [docker_install : 创建 Docker 配置目录] ************************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 配置镜像加速器] ******************************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 安装 pip] ************************************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 安装 Python 依赖(requests 和 docker)] ******************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 复制 Docker Compose 二进制文件] **************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 验证 Docker Compose 安装] ********************************************************************************************
ok: [192.168.92.20]
TASK [docker_install : 输出 Docker Compose 版本] ********************************************************************************************
skipping: [192.168.92.20]
TASK [docker_install : 创建 Docker 网络 app-network] ****************************************************************************************
ok: [192.168.92.20]
TASK [mysql : 创建 MySQL 数据目录] **********************************************************************************************************
ok: [192.168.92.20]
TASK [mysql : 创建 MySQL 初始化脚本目录] ****************************************************************************************************
ok: [192.168.92.20]
TASK [mysql : 复制 init.sql 到宿主机] *******************************************************************************************************
ok: [192.168.92.20]
TASK [mysql : 运行 MySQL 容器] **************************************************************************************************************
ok: [192.168.92.20]
TASK [redis : 创建 Redis 数据目录] **********************************************************************************************************
ok: [192.168.92.20]
TASK [redis : 运行 Redis 容器] **************************************************************************************************************
ok: [192.168.92.20]
TASK [springboot : 创建 Spring Boot 构建目录] ***********************************************************************************************
ok: [192.168.92.20]
TASK [springboot : 复制 Spring Boot JAR 包] *************************************************************************************************
ok: [192.168.92.20]
TASK [springboot : 复制 Dockerfile] *********************************************************************************************************
ok: [192.168.92.20]
TASK [springboot : 构建 Spring Boot 镜像] ***************************************************************************************************
ok: [192.168.92.20]
TASK [springboot : 运行 Spring Boot 容器] ***************************************************************************************************
ok: [192.168.92.20]
TASK [vue : 创建前端文件目录] ***************************************************************************************************************
ok: [192.168.92.20]
TASK [vue : 复制前端静态文件到宿主机] *******************************************************************************************************
changed: [192.168.92.20]
TASK [nginx : 创建 Nginx 配置目录] **********************************************************************************************************
ok: [192.168.92.20]
TASK [nginx : 生成 nginx.conf 配置文件] *****************************************************************************************************
changed: [192.168.92.20]
TASK [nginx : 启动 Nginx 容器] **************************************************************************************************************
changed: [192.168.92.20]
PLAY RECAP **********************************************************************************************************************************
192.168.92.20 : ok=35 changed=9 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
[root@ansible ansible-playbook]#
验证部署
检查各个服务状态
[root@ansible ansible-playbook]# ansible webservers -m shell -a 'docker ps'
192.168.92.20 | CHANGED | rc=0 >>
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e10821b911f3 nginx:alpine "/docker-entrypoint...." About a minute ago Up About a minute 0.0.0.0:80->80/tcp nginx
30ad93685e0d docker-compose_springboot "java -jar /app.jar" 7 minutes ago Up 2 minutes 8080/tcp springboot
d2b800954467 redis:6-alpine "docker-entrypoint.s..." 13 minutes ago Up 2 minutes 6379/tcp redis
ed7add482e27 mysql:8.0 "docker-entrypoint.s..." 13 minutes ago Up 2 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp mysql
状态没有问题
访问 Web 应用(192.168.92.20:80)

实验完成!