197 KiB
Docker Swarm 部署手册(Review 环境)
一、部署概览
环境信息
| 项目 | 值 |
|---|---|
| 主节点 | ZD-BAK-APP2 (192.168.x.132) |
| 操作系统 | CentOS 7 |
| 网络名称 | review |
| 网络子网 | 10.18.0.0/16 |
部署进度
| 阶段 | 状态 |
|---|---|
| 系统准备 | ✅ 完成 |
| 数据盘挂载 | ✅ 完成 |
| Docker 安装 | ✅ 完成 |
| Swarm 初始化 | ✅ 完成 |
| Overlay 网络 | ✅ 完成 |
| Portainer | ✅ 完成 |
| MySQL 主从复制 (mysql-repl-tool) | ✅ 完成 |
| RabbitMQ 集群 | ✅ 完成 |
| Nacos 集群 | ✅ 完成 |
| XXL-Job-Admin | ✅ 完成 |
| Canal | ✅ 完成 |
| Elasticsearch + Kibana | ✅ 完成 |
| Log (Logstash + Filebeat) | ✅ 完成 |
| SkyWalking | ✅ 完成 |
| MongoDB | ✅ 完成 |
| Redis Sentinel 集群 | ✅ 完成 |
| 后端服务 (sa-server) | ✅ 完成 |
| 前端服务 (sa-cc) | ✅ 完成 |
| Nginx 反向代理 | ✅ 完成 |
数据盘信息
| 项目 | 值 |
|---|---|
| 设备 | /dev/sdb |
| 容量 | 3.7T |
| 文件系统 | ext4 |
| UUID | 988dd535-6531-4a3e-89b7-104de26adcf2 |
| 挂载点 | /mnt/data |
| 数据目录 | /mnt/data/volumes |
集群节点信息
| 节点 | 主机名 | IP | 角色 |
|---|---|---|---|
| 节点1 | ZD-BAK-APP1 | 192.168.3.134 | Worker |
| 节点2 | ZD-BAK-APP2 | 192.168.3.132 | Manager (Leader) |
| 节点3 | zd-bak-app3 | 192.168.3.133 | Worker |
| 节点4 | ZD-FDFS4 | 192.168.3.125 | Worker |
二、系统准备
2.1 更换阿里云 YUM 源
# 备份官方源
cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
# 下载阿里云源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# 重建缓存
yum clean all && yum makecache
2.2 验证 YUM 源
grep aliyun /etc/yum.repos.d/CentOS-Base.repo
2.3 数据盘挂载配置
重要:在部署服务前,必须先完成数据盘挂载。如果先部署服务再挂载数据盘,已写入的数据将被"遮盖",需要迁移处理。
2.3.1 挂载原理
┌─────────────────────────────────────────────────────────────────┐
│ Linux 文件系统树 │
│ / │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ │ │ │ │
│ /home /mnt /var │
│ (sda3 64G) │ │
│ /data ◄── /dev/sdb 3.7T 数据盘 │
│ │ │
│ /volumes │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ /elasticsearch /mongodb /kibana │
└─────────────────────────────────────────────────────────────────┘
说明:
/dev/sdb是 3.7T 数据盘,挂载到/mnt/data- 所有写入
/mnt/data及其子目录的数据都存储在数据盘上 - 服务数据目录统一放在
/mnt/data/volumes/下
2.3.2 查看磁盘信息
# 查看所有磁盘和分区
lsblk
# 预期输出:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
# sda 8:0 0 894.3G 0 disk
# ├─sda1 8:1 0 200M 0 part /boot/efi
# ├─sda2 8:2 0 1G 0 part /boot
# ├─sda3 8:3 0 64G 0 part /home
# ├─sda4 8:4 0 31.3G 0 part [SWAP]
# └─sda5 8:5 0 797G 0 part /
# sdb 8:16 0 3.7T 0 disk ← 数据盘(待挂载或已挂载)
# 查看磁盘文件系统类型和 UUID
blkid /dev/sdb
2.3.3 挂载数据盘
# 创建挂载点
mkdir -p /mnt/data
# 如果数据盘未格式化,先格式化(慎重!会清除数据)
# mkfs.ext4 /dev/sdb
# 临时挂载(重启后失效)
mount /dev/sdb /mnt/data
# 验证挂载
df
-h /mnt/data
2.3.4 配置开机自动挂载
# 获取磁盘 UUID
blkid /dev/sdb
# 输出示例:/dev/sdb: UUID="988dd535-6531-4a3e-89b7-104de26adcf2" TYPE="ext4"
# 备份 fstab
cp /etc/fstab /etc/fstab.bak
# 添加自动挂载配置(使用 UUID 更可靠)
echo 'UUID=988dd535-6531-4a3e-89b7-104de26adcf2 /mnt/data ext4 defaults 0 0' >> /etc/fstab
# 验证 fstab 配置
cat /etc/fstab
# 测试配置是否正确(不应报错)
mount -a
# 确认挂载成功
df -h /mnt/data
fstab 格式说明:
| 字段 | 说明 | 示例值 |
|---|---|---|
| 设备 | UUID 或设备路径 | UUID=988dd535-... |
| 挂载点 | 目录路径 | /mnt/data |
| 文件系统 | ext4/xfs/等 | ext4 |
| 选项 | 挂载选项 | defaults |
| dump | 备份标志 | 0 |
| fsck | 检查顺序 | 0 |
2.3.5 创建服务数据目录
# 创建各服务数据目录
mkdir -p /mnt/data/volumes/elasticsearch
mkdir -p /mnt/data/volumes/kibana/data
mkdir -p /mnt/data/volumes/kibana/config
mkdir -p /mnt/data/volumes/mongodb
# 设置权限(Bitnami 镜像使用 UID 1001)
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana
chown -R 1001:1001 /mnt/data/volumes/mongodb
# 验证
ls -la /mnt/data/volumes/
2.3.6 常见问题
问题 1:fstab 语法错误
错误示例:
"/dev/sdb /mnt/data ext4 defaults 0 0" ← 整行被引号包裹(错误)
正确写法:
UUID=988dd535-6531-4a3e-89b7-104de26adcf2 /mnt/data ext4 defaults 0 0
问题 2:先部署服务后挂载数据盘
如果在挂载数据盘之前部署了服务,数据会写入根分区。挂载数据盘后,原数据会被"遮盖"。
解决方案:
# 1. 停止相关服务
docker stack rm review_log_es
docker stack rm review_mongodb
# 2. 卸载数据盘
umount /mnt/data
# 3. 查看原数据(此时可以看到写在根分区的数据)
ls -la /mnt/data/volumes/
du -sh /mnt/data/volumes/*
# 4. 备份原数据
mv /mnt/data /mnt/data_old
# 5. 重新创建挂载点并挂载数据盘
mkdir -p /mnt/data
mount /dev/sdb /mnt/data
# 6. 迁移数据到数据盘
cp -a /mnt/data_old/volumes /mnt/data/
# 7. 设置权限
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana
chown -R 1001:1001 /mnt/data/volumes/mongodb
# 8. 重新部署服务
cd /opt/swarm/support/elasticsearch
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es
cd /opt/swarm/support/mongodb
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb
# 9. 确认无误后删除备份
rm -rf /mnt/data_old
三、Docker 安装
3.1 卸载旧版本
sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
3.2 安装依赖工具
sudo yum install -y yum-utils
3.3 配置 Docker 仓库
# 使用阿里云镜像源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3.4 安装 Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3.5 配置 Docker Daemon
mkdir -p /etc/docker
echo '{"log-opts":{"max-size":"1g","max-file":"3"},"registry-mirrors":["https://registry.cn-hangzhou.aliyuncs.com"]}' > /etc/docker/daemon.json
配置说明:
| 配置项 | 值 | 说明 |
|---|---|---|
| log-opts.max-size | 1g | 单个日志文件最大 1GB |
| log-opts.max-file | 3 | 最多保留 3 个日志文件 |
| registry-mirrors | 阿里云 | 镜像加速器 |
3.6 启动 Docker
sudo systemctl daemon-reload
sudo systemctl start docker
sudo systemctl enable docker
# 验证
systemctl status docker
docker version
3.7 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
3.8 调整文件描述符限制
# 临时生效
ulimit -SHn 65536
# 永久生效
echo '* soft nofile 65535' >> /etc/security/limits.conf
echo '* hard nofile 65535' >> /etc/security/limits.conf
# 验证
tail -2 /etc/security/limits.conf
四、Docker Swarm 初始化
4.1 初始化集群(主节点)
docker swarm init
4.2 验证节点
docker node ls
预期输出:
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
xxx * ZD-BAK-APP2 Ready Active Leader
五、创建 Overlay 网络
docker network create \
--driver=overlay \
--subnet=10.18.0.0/16 \
--scope swarm \
--attachable \
review
验证:
docker network ls | grep review
六、部署 Portainer
6.1 配置文件
docker-compose.yml:
version: '3.2'
services:
agent:
image: portainer/agent:2.20.3
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- agent_network
deploy:
mode: global
placement:
constraints: [node.platform.os == linux]
portainer:
image: portainer/portainer-ce:2.20.3
command: -H tcp://tasks.agent:9001 --tlsskipverify
ports:
- "9443:9443"
- "9000:9000"
- "8000:8000"
volumes:
- portainer_data:/data
networks:
- agent_network
deploy:
mode: replicated
replicas: 1
placement:
constraints: [node.hostname == ZD-BAK-APP2]
networks:
agent_network:
driver: overlay
attachable: true
volumes:
portainer_data:
6.2 部署命令
cd /path/to/docker-swarm-review/portainer
docker stack deploy --compose-file docker-compose.yml portainer
6.3 验证
docker stack ps portainer
docker service ls | grep portainer
6.4 访问地址
- HTTPS:
https://<服务器IP>:9443 - HTTP:
http://<服务器IP>:9000
七、多服务器节点管理
7.1 节点角色说明
| 角色 | 说明 |
|---|---|
| Manager | 管理节点,负责集群调度和状态管理 |
| Worker | 工作节点,只运行容器 |
建议:3 节点集群至少 1 个 Manager,生产环境建议 3 个 Manager。
7.2 新节点准备工作
在新服务器上依次执行(同主节点):
- 更换阿里云 YUM 源
- 安装 Docker
- 配置 Docker Daemon
- 启动 Docker
- 关闭防火墙
- 调整文件描述符限制
7.3 获取加入 Token(主节点执行)
# 获取 Worker 加入 Token
docker swarm join-token worker
# 获取 Manager 加入 Token
docker swarm join-token manager
7.4 加入集群(新节点执行)
docker swarm join --token SWMTKN-1-xxxxx <主节点IP>:2377
7.5 验证节点(主节点执行)
docker node ls
预期输出:
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
xxx * ZD-BAK-APP2 Ready Active Leader
xxx 节点2 Ready Active
xxx 节点3 Ready Active
7.6 节点标签管理
# 添加标签
docker node update --label-add <标签名>=<值> <节点名>
# 示例:标记 MySQL 运行节点
docker node update --label-add review_mysql=1 ZD-BAK-APP2
# 查看节点标签
docker node inspect <节点名> --format '{{.Spec.Labels}}'
7.7 节点管理命令
# 查看所有节点
docker node ls
# 将节点设为 Drain(停止接收新任务)
docker node update --availability drain <节点名>
# 将节点恢复为 Active
docker node update --availability active <节点名>
# 移除节点(在主节点执行)
docker node rm <节点名>
# 离开集群(在该节点执行)
docker swarm leave
docker swarm leave --force # Manager 节点需要 --force
八、Docker Config 管理
8.1 什么是 Docker Config
Docker Config 用于在 Swarm 集群中统一管理配置文件,无需在每个节点放置文件。
8.2 创建 Config
# 从文件创建
docker config create <config名称> <文件路径>
# 示例
docker config create review_mysql_conf_v1 /tmp/mysql_custom.cnf
8.3 查看 Config
docker config ls
docker config inspect <config名称>
8.4 在 docker-compose.yml 中引用
services:
myservice:
configs:
- source: my_config
target: /path/in/container/config.conf
configs:
my_config:
external: true
name: my_config_name
8.5 删除 Config
docker config rm <config名称>
注意:删除前需确保没有服务在使用该 Config。
九、Docker 挂载详解
9.1 Docker 挂载类型
┌─────────────────────────────────────────────────────────────────┐
│ Docker 挂载类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Bind Mount(绑定挂载)← 本环境使用的方式 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 宿主机 │ │ 容器 │ │
│ │ /mnt/data/ │ ═════► │ /bitnami/ │ │
│ │ volumes/ │ │ mongodb │ │
│ │ mongodb │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ - 直接映射宿主机目录到容器 │
│ - 路径由用户指定 │
│ - 适合需要在特定位置存储数据 │
│ │
│ 2. Volume(Docker 管理卷) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Docker 管理 │ │ 容器 │ │
│ │ /var/lib/ │ ═════► │ /bitnami/ │ │
│ │ docker/ │ │ mongodb │ │
│ │ volumes/xxx │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ - Docker 自动管理存储位置 │
│ - 默认在 /var/lib/docker/volumes/ │
│ - 便于 Docker 管理,但位置不可控 │
│ │
│ 3. tmpfs(内存挂载) │
│ - 数据存储在内存中 │
│ - 容器停止后数据丢失 │
│ - 用于临时敏感数据 │
└─────────────────────────────────────────────────────────────────┘
9.2 docker-compose.yml 中的挂载配置
Bind Mount 方式(本环境使用):
volumes:
- '/mnt/data/volumes/mongodb:/bitnami/mongodb'
# ↑ 宿主机路径 ↑ 容器内路径
Volume 方式(Docker 管理):
services:
db:
volumes:
- 'mongodb_data:/bitnami/mongodb'
volumes:
mongodb_data:
driver: local
9.3 验证挂载是否生效
方法 1:检查容器挂载信息
# 检查 ES 容器挂载
docker inspect $(docker ps -q -f name=review_log_es_elasticsearch) --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
# 检查 MongoDB 容器挂载
docker inspect $(docker ps -q -f name=review_mongodb_db) --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
# 预期输出:
# bind: /mnt/data/volumes/elasticsearch -> /bitnami/elasticsearch/data
# bind: /mnt/data/volumes/mongodb -> /bitnami/mongodb
方法 2:确认数据写入位置
# 查看宿主机目录是否有数据生成
ls -la /mnt/data/volumes/elasticsearch/
ls -la /mnt/data/volumes/mongodb/
# 查看数据大小
du -sh /mnt/data/volumes/*
方法 3:确认数据在数据盘上
# 确认路径属于 /dev/sdb(3.7T 数据盘)
df -h /mnt/data/volumes/elasticsearch
df -h /mnt/data/volumes/mongodb
# 预期输出应显示 /dev/sdb 和 3.7T 容量
方法 4:写入测试文件验证
# 在宿主机创建测试文件
echo "test from host" > /mnt/data/volumes/mongodb/test_host.txt
# 进入容器查看是否存在
docker exec $(docker ps -q -f name=review_mongodb_db) cat /bitnami/mongodb/test_host.txt
# 在容器内创建测试文件
docker exec $(docker ps -q -f name=review_mongodb_db) sh -c 'echo "test from container" > /bitnami/mongodb/test_container.txt'
# 在宿主机查看
cat /mnt/data/volumes/mongodb/test_container.txt
# 清理测试文件
rm /mnt/data/volumes/mongodb/test_*.txt
如果双向都能看到文件,说明挂载完全正确。
十、常见问题及解决
10.1 镜像拉取失败
问题: dial tcp xxx:443: i/o timeout
解决方案:
方案 1:更换镜像加速器
echo '{"log-opts":{"max-size":"1g","max-file":"3"},"registry-mirrors":["https://registry.cn-hangzhou.aliyuncs.com"]}' > /etc/docker/daemon.json
systemctl daemon-reload
systemctl restart docker
方案 2:使用其他源拉取后打标签
docker pull <其他源>/<镜像名>
docker tag <其他源>/<镜像名> <原镜像名>
方案 3:从其他机器导入镜像
# 在能访问的机器上导出
docker save <镜像名> -o image.tar
# 传输到目标机器后导入
docker load -i image.tar
方案 4:通过私有仓库中转(推荐)
如果有私有镜像仓库(如 Harbor),可以先在能访问外网的服务器拉取镜像,推送到私有仓库,再从私有仓库拉取:
# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上操作
docker pull <原镜像名>:<版本>
docker tag <原镜像名>:<版本> harbor.sino-assist.com/library/<镜像名>:<版本>
docker login harbor.sino-assist.com
docker push harbor.sino-assist.com/library/<镜像名>:<版本>
# 2. 在目标服务器(如 132 服务器)上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/library/<镜像名>:<版本>
docker tag harbor.sino-assist.com/library/<镜像名>:<版本> <原镜像名>:<版本>
示例(Canal 镜像):
# 50 服务器上
docker pull canal/canal-server:v1.1.5
docker tag canal/canal-server:v1.1.5 harbor.sino-assist.com/library/canal-server:v1.1.5
docker push harbor.sino-assist.com/library/canal-server:v1.1.5
# 132 服务器上
docker pull harbor.sino-assist.com/library/canal-server:v1.1.5
docker tag harbor.sino-assist.com/library/canal-server:v1.1.5 canal/canal-server:v1.1.5
10.2 镜像标签打错处理
问题: 给镜像打标签时打错了(如版本号或仓库名写错)
解决方案:
删除本地错误标签:
# 删除错误的本地标签(只删除标签,不删除镜像本身)
docker rmi <错误的标签名>
# 示例:删除打错的标签
docker rmi harbor.sino-assist.com/bitnami/redis:wrong-version
# 然后重新打正确的标签
docker tag <原镜像名>:<版本> <正确的标签名>
删除已推送到 Harbor 的错误标签:
# 方法1:通过 Harbor Web 界面删除
# 登录 Harbor -> 项目 -> 仓库 -> 选择镜像 -> 删除标签
# 方法2:通过 API 删除(需要管理员权限)
# 1. 先获取镜像的 digest
curl -u <用户名>:<密码> -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
https://harbor.sino-assist.com/v2/<项目>/<仓库>/manifests/<标签> -I | grep Docker-Content-Digest
# 2. 使用 digest 删除
curl -X DELETE -u <用户名>:<密码> \
https://harbor.sino-assist.com/v2/<项目>/<仓库>/manifests/<digest>
示例:
# 场景:不小心把 redis:7.0.11 标签打成了 redis:7.0.1
docker rmi harbor.sino-assist.com/bitnami/redis:7.0.1
# 重新打正确标签
docker tag bitnami/redis:7.0.11 harbor.sino-assist.com/bitnami/redis:7.0.11
docker push harbor.sino-assist.com/bitnami/redis:7.0.11
注意:
docker rmi删除的是标签引用。如果镜像只有这一个标签,镜像本身也会被删除。如果镜像有多个标签引用,只会删除指定的标签。
10.3 Portainer 超时锁定
问题: Your Portainer instance timed out for security purposes
解决:
docker service update --force portainer_portainer
然后立即访问并设置管理员密码(5 分钟内)。
10.4 iptables 规则错误
问题: iptables: No chain/target/match by that name
解决:
systemctl restart docker
10.5 EOF Heredoc 语法问题
问题: 多行内容写入文件时出现 > 等待符号
原因:
- 结束的
EOF前面有空格或缩进 - Windows 与 Linux 换行符不一致
解决: 使用单行 echo 或 vim 手动创建:
echo '内容' > /path/to/file
或:
vim /path/to/file
# 粘贴内容后 :wq 保存
10.6 Windows 换行符问题
问题: yaml: line xx: could not find expected ':'
解决:
sed -i 's/\r$//' /path/to/file
10.7 服务状态为 Ready 不变为 Running
问题: docker stack ps 显示服务状态一直是 Ready 或 Preparing
排查:
docker stack ps <stack名称> --no-trunc
docker service logs <服务名>
常见原因:
- 镜像拉取失败
- 节点标签不匹配
- 资源不足
10.8 Swarm 跨节点服务报 "No such image"
问题: docker stack ps 显示 Rejected: No such image
原因: Swarm 跨节点部署时,目标节点上没有所需镜像,且无法从镜像仓库拉取。
解决: 在目标节点上手动拉取镜像:
# 方法1:直接拉取
docker pull <镜像名>
# 方法2:使用国内镜像源
docker pull dockerproxy.cn/<镜像名>
docker tag dockerproxy.cn/<镜像名> <原镜像名>
# 方法3:从其他机器导入
docker save <镜像名> -o image.tar
# 传输到目标节点后
docker load -i image.tar
10.9 MySQL Slave 不断重启
问题: MySQL Slave 服务启动后约 90 秒被关闭,状态显示 Complete 后重新启动。
原因: Bitnami MySQL 的 healthcheck 脚本会检查复制状态,如果复制未建立或有问题,healthcheck 失败导致容器重启。
排查:
# 查看日志确认是否被 SHUTDOWN 信号终止
docker service logs <slave服务名> 2>&1 | grep -i "shutdown"
# 在 Slave 运行期间检查复制状态
docker exec $(docker ps -q -f name=mysql-slave) mysql -uroot -p'<密码>' -e "SHOW SLAVE STATUS\G"
解决方案:
方案 1:调整 healthcheck 参数(推荐)
增加 start_period 给足够的初始化时间:
healthcheck:
test: ['CMD', '/opt/bitnami/scripts/mysql/healthcheck.sh']
interval: 30s
timeout: 10s
retries: 5
start_period: 180s
方案 2:删除 Slave 数据卷重新初始化
如果 Slave 数据卷有旧数据,会跳过复制配置初始化:
docker stack rm <stack名称>
sleep 15
docker volume rm <slave数据卷名称>
# 重新部署
10.10 Swarm 节点间通信问题
问题: 跨节点的服务无法通过 overlay 网络通信
排查:
# 检查节点状态
docker node ls
# 测试节点间基础连通性
ping <目标节点IP>
# 测试 Swarm 通信端口 (7946)
timeout 3 bash -c "echo >/dev/tcp/<目标节点IP>/7946" && echo "OK" || echo "FAILED"
# 测试 overlay 网络内服务连通性
docker run --rm --network <网络名> <镜像> <测试命令>
解决:
- 确保所有节点防火墙已关闭:
systemctl stop firewalld
systemctl disable firewalld
- 或者开放 Swarm 所需端口:
firewall-cmd --permanent --add-port=2377/tcp # 集群管理(仅 Manager)
firewall-cmd --permanent --add-port=7946/tcp # 节点间通信
firewall-cmd --permanent --add-port=7946/udp
firewall-cmd --permanent --add-port=4789/udp # Overlay 网络
firewall-cmd --reload
10.11 复制状态为空(SHOW SLAVE STATUS 无输出)
问题: 进入 Slave 容器执行 SHOW SLAVE STATUS 没有任何输出
原因: Slave 初始化时没有配置复制,可能是:
- 数据卷已存在旧数据,跳过了初始化
- 环境变量未正确传递
- 初始化时无法连接 Master
排查:
# 检查环境变量是否正确
docker service inspect <slave服务名> --pretty | grep -A 30 "Env"
# 查看初始化日志是否有复制配置
docker service logs <slave服务名> 2>&1 | grep -iE "repl|slave|master|configur"
解决:
# 删除 stack 和 slave 数据卷
docker stack rm <stack名称>
sleep 15
docker volume rm <slave数据卷名称>
# 确保 Master 已启动并可连接
docker run --rm --network <网络名> <mysql镜像> mysql -h <master服务名> -uroot -p'<密码>' -e "SELECT 1"
# 重新部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - <stack名称>
10.12 镜像名称拼写错误
问题: 部署时提示镜像拉取失败,但镜像名看起来正确
原因: 镜像名称可能有拼写错误,如 rabbitmg 应为 rabbitmq
排查:
# 检查 docker-compose.yml 中的镜像名
grep -i "image:" docker-compose.yml
解决: 修正镜像名称后重新部署
10.13 RabbitMQ 集群节点无法加入
问题: RabbitMQ 队列节点无法加入 stats 节点组成集群
原因:
- ERL_COOKIE 不一致
- 节点间网络不通
- stats 节点尚未启动完成
排查:
# 检查集群状态
docker exec $(docker ps -q -f name=rabbitmq_stats) rabbitmqctl cluster_status
# 检查节点日志
docker service logs <队列服务名> --tail 100
解决:
# 确保 ERL_COOKIE 一致(所有节点必须相同)
# 检查 docker-compose.yml 中 RABBITMQ_ERL_COOKIE 配置
# 删除数据卷重新初始化
docker stack rm <stack名称>
sleep 15
docker volume ls | grep rabbitmq | awk '{print $2}' | xargs docker volume rm
# 重新部署
10.14 RabbitMQ 网络分区 (Network Partition)
问题: rabbitmqctl cluster_status 显示 Network Partitions 不为空
原因: 节点间网络中断后恢复,导致集群分裂
解决:
# 方法1:重启受影响的节点
docker service update --force <受影响的服务名>
# 方法2:手动解决分区(在 stats 节点执行)
docker exec $(docker ps -q -f name=rabbitmq_stats) rabbitmqctl forget_cluster_node <分区节点名>
docker service update --force <分区节点服务名>
10.15 RabbitMQ 无法登录管理界面
问题: 部署成功但无法使用配置的用户名密码登录管理界面
原因:
- 用户不存在
- 密码不正确
- 用户没有管理员权限
排查:
# 查看用户列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users
解决:
# 如果用户不存在,创建用户
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl add_user root gkxl650
# 设置管理员权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_user_tags root administrator
# 设置 vhost 权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p /review root ".*" ".*" ".*"
# 如果用户存在但密码不对,重置密码
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl change_password root <新密码>
十一、部署 MySQL 主从复制 (mysql-repl-tool)
10.1 服务说明
用于 Nacos、XXL-Job 等工具类服务的专用 MySQL 数据库,采用主从复制架构。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_tool_mysql |
| 镜像 | bitnami/mysql:8.0 |
| Master 节点 | ZD-BAK-APP1 |
| Master 端口 | 25306 |
| Slave 节点 | ZD-BAK-APP2 |
| Slave 端口 | 25307 |
10.2 部署前准备
创建 MySQL 配置文件
# Master 配置
cat > /tmp/mysql_master_custom.cnf << 'EOF'
[mysqld]
max_connections=500
max_allowed_packet=64M
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
log-bin=mysql-bin
binlog-format=ROW
server_id=1
EOF
# Slave 配置
cat > /tmp/mysql_slave_custom.cnf << 'EOF'
[mysqld]
max_connections=500
max_allowed_packet=64M
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
log-bin=mysql-bin
binlog-format=ROW
server_id=2
read_only=1
EOF
创建 Docker Config
docker config create review_tool_mysql_master_conf_v1 /tmp/mysql_master_custom.cnf
docker config create review_tool_mysql_slave_conf_v1 /tmp/mysql_slave_custom.cnf
# 验证
docker config ls | grep review_tool
确保所有节点都有镜像
重要:Swarm 跨节点部署时,每个节点都需要有镜像。
# 在每个节点上拉取镜像
docker pull bitnami/mysql:8.0
# 如果官方源拉取失败,使用国内镜像源
docker pull dockerproxy.cn/bitnami/mysql:8.0
docker tag dockerproxy.cn/bitnami/mysql:8.0 bitnami/mysql:8.0
确保所有节点防火墙已关闭
# 在每个节点执行
systemctl stop firewalld
systemctl disable firewalld
10.3 部署命令
cd /path/to/docker-swarm-review/mysql-repl-tool
# 转换 Windows 换行符(如果文件从 Windows 传输)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_tool_mysql
10.4 验证部署
# 查看服务状态
docker service ls | grep review_tool_mysql
# 预期输出:
# review_tool_mysql_mysql-master replicated 1/1
# review_tool_mysql_mysql-slave replicated 1/1
# 查看详细状态
docker stack ps review_tool_mysql
# 验证复制状态
docker exec $(docker ps -q -f name=mysql-slave) mysql -uroot -p'gkxl2024#@' -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO|Slave_SQL|Seconds_Behind"
# 预期输出:
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes
# Seconds_Behind_Master: 0
10.5 连接信息
| 项目 | Master | Slave |
|---|---|---|
| 主机 | 192.168.3.134 | ZD-BAK-APP2 IP |
| 端口 | 25306 | 25307 |
| Root 密码 | gkxl2024#@ | gkxl2024#@ |
| 普通用户 | zd_tool | zd_tool |
| 普通密码 | gkxl2024#@ | gkxl2024#@ |
10.6 常用运维命令
# 查看服务日志
docker service logs review_tool_mysql_mysql-master --tail 50
docker service logs review_tool_mysql_mysql-slave --tail 50
# 进入 Master 容器
docker exec -it $(docker ps -q -f name=mysql-master) bash
# 进入 Slave 容器
docker exec -it $(docker ps -q -f name=mysql-slave) bash
# 强制重启服务
docker service update --force review_tool_mysql_mysql-slave
# 删除并重新部署
docker stack rm review_tool_mysql
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_tool_mysql
十二、部署 RabbitMQ 集群
11.1 服务说明
RabbitMQ 集群采用 3 节点架构,包含 1 个管理节点 (stats) 和 2 个队列节点 (queue1, queue2)。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_rabbitmq |
| 镜像 | harbor.sino-assist.com/bitnami/rabbitmq:3.11 |
| 管理界面端口 | 15672 |
| AMQP 端口 | 5672 |
| STOMP 端口 | 61613 |
| WebSocket 端口 | 15674 |
11.2 节点分布
| 服务 | 类型 | 节点标签 | 部署节点 |
|---|---|---|---|
| stats | 管理节点 | rabbit_stats==1 |
ZD-BAK-APP2 |
| queue1 | 队列节点 (disc) | rabbit_queue1==1 |
ZD-BAK-APP1 |
| queue2 | 队列节点 (disc) | rabbit_queue2==1 |
zd-bak-app3 |
11.3 部署前准备
添加节点标签
# stats 部署到 ZD-BAK-APP2
docker node update --label-add rabbit_stats=1 ZD-BAK-APP2
# queue1 部署到 ZD-BAK-APP1
docker node update --label-add rabbit_queue1=1 ZD-BAK-APP1
# queue2 部署到 zd-bak-app3
docker node update --label-add rabbit_queue2=1 zd-bak-app3
# 验证标签
docker node ls -q | xargs -I {} docker node inspect {} --format '{{.Description.Hostname}}: {{.Spec.Labels}}'
确保所有节点有镜像
# 在每个目标节点上执行
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/rabbitmq:3.11
11.4 部署命令
cd /path/to/docker-swarm-review/rabbitmq
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose-review.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose-review.yml | docker stack deploy --compose-file - review_rabbitmq
11.5 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_rabbitmq
# 预期输出:
# review_rabbitmq_stats replicated 1/1
# review_rabbitmq_queue1 replicated 1/1
# review_rabbitmq_queue2 replicated 1/1
# 查看详细状态
docker stack ps review_rabbitmq --no-trunc | grep -v Shutdown
检查集群状态
# 进入 stats 节点检查集群状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl cluster_status
预期输出应包含:
Running Nodes: rabbit@stats, rabbit@queue1, rabbit@queue2Alarms: (none)Network Partitions: (none)
检查节点健康状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl node_health_check
查看集群中的所有节点
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_nodes
11.6 连接信息
| 项目 | 值 |
|---|---|
| 管理界面 | http://<ZD-BAK-APP2_IP>:15672 |
| 用户名 | root |
| 密码 | gkxl650 |
| AMQP 连接 | amqp://root:gkxl650@<服务器IP>:5672/review |
| Virtual Host | /review |
11.7 用户管理
查看用户列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users
如果无法登录,重置密码
# 修改用户密码
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl change_password root <新密码>
如果用户不存在,创建用户
# 创建用户
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl add_user root gkxl650
# 设置管理员权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_user_tags root administrator
# 设置 vhost 权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p /review root ".*" ".*" ".*"
删除用户
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl delete_user <用户名>
11.8 常用运维命令
# 查看服务日志
docker service logs review_rabbitmq_stats --tail 50
docker service logs review_rabbitmq_queue1 --tail 50
docker service logs review_rabbitmq_queue2 --tail 50
# 进入 stats 容器
docker exec -it $(docker ps -q -f name=review_rabbitmq_stats) bash
# 查看集群状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl cluster_status
# 查看队列列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_queues
# 查看连接列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_connections
# 删除并重新部署
docker stack rm review_rabbitmq
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose-review.yml | docker stack deploy --compose-file - review_rabbitmq
十三、部署 Nacos 集群
12.1 服务说明
Nacos 作为服务注册中心和配置中心,采用 3 节点集群模式部署,使用 MySQL 作为持久化存储。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_nacos |
| 镜像 | nacos/nacos-server:v2.3.0 |
| 集群模式 | cluster |
| 数据库 | MySQL (review-tool-mysql-master) |
12.2 节点分布
| 服务 | 部署节点 | Web 端口 | gRPC 客户端端口 | gRPC 集群端口 |
|---|---|---|---|---|
| nacos1 | ZD-BAK-APP1 | 21848 | 22848 | 22849 |
| nacos2 | ZD-BAK-APP2 | 23848 | 24848 | 24849 |
| nacos3 | zd-bak-app3 | 25848 | 26848 | 26849 |
12.3 部署前准备
创建 Nacos 数据库
# 连接 MySQL Master
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@'
# 创建数据库
CREATE DATABASE nacos CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 授权用户
GRANT ALL PRIVILEGES ON nacos.* TO 'zd_tool'@'%';
FLUSH PRIVILEGES;
exit;
导入 Nacos Schema
# 导入建表脚本
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' nacos < /path/to/nacos-cluser/mysql-schema.sql
拉取镜像
在每个目标节点上拉取镜像:
docker pull nacos/nacos-server:v2.3.0
12.4 部署命令
cd /path/to/docker-swarm-review/nacos-cluser
# 转换 Windows 换行符
sed -i 's/\r$//' cluster-docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./cluster-docker-compose.yml | docker stack deploy --compose-file - review_nacos
12.5 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_nacos
# 预期输出:
# review_nacos_nacos1 replicated 1/1
# review_nacos_nacos2 replicated 1/1
# review_nacos_nacos3 replicated 1/1
# 查看详细状态
docker stack ps review_nacos --no-trunc | grep -v Shutdown
检查集群状态
通过 API 检查集群节点:
curl -X GET "http://ZD-BAK-APP1:21848/nacos/v1/ns/operator/cluster/nodes"
或登录 Web 控制台,进入 集群管理 -> 节点列表 查看 3 个节点是否都处于 UP 状态。
12.6 连接信息
| 项目 | 值 |
|---|---|
| 控制台 (nacos1) | http://ZD-BAK-APP1:21848/nacos |
| 控制台 (nacos2) | http://ZD-BAK-APP2:23848/nacos |
| 控制台 (nacos3) | http://zd-bak-app3:25848/nacos |
| 用户名 | nacos |
| 密码 | nacos |
12.7 端口说明
| 端口 | 用途 |
|---|---|
| 8848 | Web 控制台和 HTTP API |
| 9848 | gRPC 客户端通信端口 |
| 9849 | gRPC 集群节点间通信端口 |
注意:客户端连接时需配置 8848 端口,gRPC 端口会自动计算(默认 8848+1000)
12.8 常用运维命令
# 查看服务日志
docker service logs review_nacos_nacos1 --tail 50
docker service logs review_nacos_nacos2 --tail 50
docker service logs review_nacos_nacos3 --tail 50
# 强制重启服务
docker service update --force review_nacos_nacos1
# 删除并重新部署
docker stack rm review_nacos
sleep 15
env $(cat ./env_review | xargs) envsubst < ./cluster-docker-compose.yml | docker stack deploy --compose-file - review_nacos
12.9 常见问题
Nacos 启动失败:No DataSource set
原因: 数据库连接失败或用户没有权限
解决:
# 检查数据库连接
mysql -h ZD-BAK-APP1 -P 25306 -uzd_tool -p'gkxl2024#@' -e "USE nacos; SHOW TABLES;"
# 如果权限不足,重新授权
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' -e "GRANT ALL PRIVILEGES ON nacos.* TO 'zd_tool'@'%'; FLUSH PRIVILEGES;"
# 重启 Nacos 服务
docker service update --force review_nacos_nacos1
docker service update --force review_nacos_nacos2
docker service update --force review_nacos_nacos3
集群节点无法发现彼此
原因: 节点间网络不通或 gRPC 端口未开放
解决:
# 确保防火墙已关闭
systemctl stop firewalld
systemctl disable firewalld
# 或开放 Nacos 所需端口
firewall-cmd --permanent --add-port=8848/tcp
firewall-cmd --permanent --add-port=9848/tcp
firewall-cmd --permanent --add-port=9849/tcp
firewall-cmd --reload
十四、部署 XXL-Job-Admin
13.1 服务说明
XXL-Job 是分布式任务调度平台,Admin 为调度中心。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_xxl_job |
| 镜像 | xuxueli/xxl-job-admin:2.4.1 |
| 端口 | 9991 |
| 副本数 | 2 |
| 数据库 | MySQL (review-tool-mysql-master) |
13.2 部署前准备
创建 xxl_job 数据库
# 连接 MySQL Master
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@'
# 创建数据库
CREATE DATABASE xxl_job CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 授权用户
GRANT ALL PRIVILEGES ON xxl_job.* TO 'zd_tool'@'%';
FLUSH PRIVILEGES;
exit;
导入 XXL-Job Schema
从官方获取建表脚本:https://github.com/xuxueli/xxl-job/blob/master/doc/db/tables_xxl_job.sql
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' xxl_job < tables_xxl_job.sql
添加节点标签
# 选择 2 个节点运行 xxl-job-admin
docker node update --label-add xxl_job_admin=1 ZD-BAK-APP1
docker node update --label-add xxl_job_admin=1 ZD-BAK-APP2
# 验证标签
docker node ls -q | xargs -I {} docker node inspect {} --format '{{.Description.Hostname}}: {{.Spec.Labels}}'
拉取镜像
在目标节点上拉取镜像:
docker pull xuxueli/xxl-job-admin:2.4.1
13.3 部署命令
cd /path/to/docker-swarm-review/xxl-job-admin
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_xxl_job
13.4 验证部署
# 查看服务状态
docker service ls | grep review_xxl_job
# 预期输出:
# review_xxl_job_server replicated 2/2
# 查看详细状态
docker stack ps review_xxl_job --no-trunc | grep -v Shutdown
13.5 连接信息
| 项目 | 值 |
|---|---|
| 控制台 | http://<节点IP>:9991/xxl-job-admin |
| 用户名 | admin |
| 密码 | 123456 |
13.6 常用运维命令
# 查看服务日志
docker service logs review_xxl_job_server --tail 50
# 强制重启服务
docker service update --force review_xxl_job_server
# 删除并重新部署
docker stack rm review_xxl_job
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_xxl_job
13.7 常见问题
启动失败:Communications link failure
原因: 无法连接 MySQL 数据库
解决:
# 检查数据库连接
mysql -h ZD-BAK-APP1 -P 25306 -uzd_tool -p'gkxl2024#@' -e "USE xxl_job; SHOW TABLES;"
# 确认 env_review 中的数据库配置正确
cat env_review | grep DATASOURCE
启动失败:Table 'xxl_job.xxl_job_info' doesn't exist
原因: 数据库 Schema 未导入
解决: 导入 XXL-Job 官方建表脚本
十五、部署 Canal
14.1 服务说明
Canal 是阿里巴巴开源的 MySQL binlog 增量订阅&消费组件,用于将 MySQL 数据变更实时同步到其他系统。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_canal |
| 镜像 | canal/canal-server:v1.1.5 |
| 数据源 | 192.168.3.123:3306 |
| 输出模式 | RabbitMQ |
| 部署节点 | ZD-BAK-APP2 |
14.2 部署前准备
确保 MySQL 开启 binlog
Canal 依赖 MySQL 的 binlog,需确保数据源 MySQL 已开启:
-- 检查 binlog 是否开启
SHOW VARIABLES LIKE 'log_bin';
-- 检查 binlog 格式(需要为 ROW)
SHOW VARIABLES LIKE 'binlog_format';
确保 MySQL 用户有复制权限
Canal 用户需要以下权限:
SELECT- 查询表结构REPLICATION SLAVE- 读取 binlogREPLICATION CLIENT- 查看 binlog 状态
-- Canal 使用的用户需要有以下权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
验证 MySQL 用户权限
由于部署服务器上没有安装 mysql 客户端,使用已部署的 MySQL 容器来测试连接:
# 通过 MySQL Master 容器连接外部 MySQL 验证权限
docker exec $(docker ps -q -f name=review_tool_mysql_mysql-master) mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SELECT 1; SHOW GRANTS; SHOW MASTER STATUS"
预期输出:
1
1
Grants for repl@%
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `repl`@`%`
File Position Binlog_Do_DB Binlog_Ignore_DB Executed_Gtid_Set
mysql-bin.015149 132458641 zd_rescue,xxl-job,nacos_job information_schema,mysql,performance_schema,sys
如果权限不足(缺少 SELECT 或 REPLICATION CLIENT),需要在 MySQL 源服务器上执行授权。
添加节点标签
docker node update --label-add review_canal=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
拉取镜像
方法一:直接从 Docker Hub 拉取(如网络可达)
docker pull canal/canal-server:v1.1.5
方法二:通过私有仓库中转(推荐)
如果目标服务器无法访问 Docker Hub,可通过私有仓库 harbor.sino-assist.com 中转:
# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上拉取并推送到私有仓库
docker pull canal/canal-server:v1.1.5
docker tag canal/canal-server:v1.1.5 harbor.sino-assist.com/library/canal-server:v1.1.5
docker login harbor.sino-assist.com
docker push harbor.sino-assist.com/library/canal-server:v1.1.5
# 2. 在目标服务器(132/ZD-BAK-APP2)上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/library/canal-server:v1.1.5
docker tag harbor.sino-assist.com/library/canal-server:v1.1.5 canal/canal-server:v1.1.5
14.3 配置说明
env_review 配置项:
| 配置项 | 值 | 说明 |
|---|---|---|
| canal_instance_master_address | 192.168.3.123:3306 | MySQL 数据源地址 |
| canal_instance_dbUsername | repl | MySQL 用户名 |
| canal_instance_dbPassword | nczl@sino_db | MySQL 密码 |
| canal_instance_filter_regex | zd_rescue.* | 订阅的表正则表达式 |
| canal_mq_topic | canal_mysql_bin | MQ Topic 名称 |
| rabbitmq_host | review-rabbitmq-stats:5672 | RabbitMQ 地址(overlay 网络内) |
| rabbitmq_exchange | canal_exchange | RabbitMQ Exchange |
| rabbitmq_username | root | RabbitMQ 用户名 |
| rabbitmq_password | gkxl650 | RabbitMQ 密码 |
| rabbitmq_virtual_host | review | RabbitMQ VHost |
14.4 部署命令
cd /path/to/docker-swarm-review/canal
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal
14.5 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_canal
# 预期输出:
# review_canal_db replicated 1/1
# 查看详细状态
docker stack ps review_canal --no-trunc | grep -v Shutdown
查看日志
docker service logs review_canal_db --tail 100
日志中应能看到:
destination:example start successful表示启动成功position:xxx表示已连接 MySQL 并获取 binlog 位置
验证 RabbitMQ 接收
登录 RabbitMQ 管理界面,检查:
- Exchange
canal_exchange是否已创建 - 是否有消息进入
14.6 连接信息
| 项目 | 值 |
|---|---|
| MySQL 数据源 | 192.168.3.123:3306 |
| RabbitMQ | review-rabbitmq-stats:5672 |
| RabbitMQ Exchange | canal_exchange |
| RabbitMQ VHost | review |
14.7 常用运维命令
# 查看服务日志
docker service logs review_canal_db --tail 50
# 强制重启服务
docker service update --force review_canal_db
# 删除并重新部署
docker stack rm review_canal
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal
14.8 常见问题
启动失败:Authentication failed
原因: MySQL 用户名或密码错误,或用户没有复制权限
解决:
# 检查 MySQL 连接
mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SHOW MASTER STATUS"
# 如果连接失败,在 MySQL 源服务器上授权
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
启动失败:Could not find first log file name in binary log index file
原因: binlog 文件不存在或已被清理
解决:
# 检查 MySQL binlog 状态
mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SHOW MASTER STATUS; SHOW BINARY LOGS;"
# 如果 binlog 被清理,需要重新初始化 Canal
docker stack rm review_canal
# 删除 Canal 数据卷(如有)
docker volume ls | grep canal | awk '{print $2}' | xargs docker volume rm
# 重新部署
无法连接 RabbitMQ
原因: RabbitMQ 地址错误或网络不通
解决:
# 确保 Canal 和 RabbitMQ 在同一 overlay 网络
docker network inspect review
# 测试网络连通性(在 Canal 容器内)
docker exec $(docker ps -q -f name=review_canal_db) ping review-rabbitmq-stats
# 检查 RabbitMQ VHost 是否存在
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_vhosts
15. FastDFS
15.1 服务说明
| 项目 | 说明 |
|---|---|
| 镜像 | harbor.sino-assist.com/season/fastdfs:1.2 |
| 部署方式 | Docker Compose(非 Swarm) |
| Tracker 端口 | 22122 |
| Storage 端口 | 23000 |
| HTTP 访问端口 | 8088 |
| 数据目录 | /opt/fastdfs/ |
| 当前部署机器 | 192.168.1.171 |
15.2 目录结构
/opt/fastdfs/
├── docker-compose.yml # 容器编排配置
├── storage.conf # storage 配置(挂载进容器)
├── nginx.conf # nginx 配置(挂载进容器)
├── tracker_data/ # tracker 数据目录
├── storage_base_path/ # storage base 目录
└── store_path0/ # 文件实际存储目录
15.3 docker-compose.yml
version: '3'
services:
tracker:
image: harbor.sino-assist.com/season/fastdfs:1.2
container_name: tracker
network_mode: host
restart: always
volumes:
- "./tracker_data:/fastdfs/tracker/data"
command: "tracker"
storage:
image: harbor.sino-assist.com/season/fastdfs:1.2
container_name: storage
network_mode: host
restart: always
volumes:
- "./storage.conf:/fdfs_conf/storage.conf"
- "./storage_base_path:/fastdfs/storage/data"
- "./store_path0:/fastdfs/store_path"
environment:
TRACKER_SERVER: "<本机IP>:22122"
command: "storage"
nginx:
image: harbor.sino-assist.com/season/fastdfs:1.2
container_name: fdfs-nginx
network_mode: host
restart: always
volumes:
- "./nginx.conf:/etc/nginx/conf/nginx.conf"
- "./store_path0:/fastdfs/store_path"
environment:
TRACKER_SERVER: "<本机IP>:22122"
command: "nginx"
迁移到新机器时,将
TRACKER_SERVER中的 IP 替换为新机器 IP。
15.4 storage.conf 关键配置
base_path=/fastdfs/storage
store_path_count=1
store_path0=/fastdfs/store_path
tracker_server=<本机IP>:22122
http.server_port=8888
15.5 nginx.conf 说明
- 监听端口:
8088 - 文件访问路径:
/group1/M00 - 文件存储根目录:
/fastdfs/storage/data(容器内路径) - 已配置跨域
Access-Control-Allow-Origin: *
15.6 常用运维命令
# 启动
cd /opt/fastdfs && docker compose up -d
# 停止
cd /opt/fastdfs && docker compose stop
# 查看状态
docker ps | grep -E 'tracker|storage|nginx'
# 查看日志
docker logs tracker
docker logs storage
docker logs fdfs-nginx
# 验证端口
ss -tlnp | grep -E '22122|23000|8088'
15.7 迁移步骤
适用于将 FastDFS 从一台机器迁移到另一台机器(可停服)。
1. 在目标机器安装 Docker
CentOS 7 需先修复官方源(已停止维护):
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all && yum makecache
yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker && systemctl start docker
Docker Engine 26.x 已内置 Compose 插件,无需单独安装 docker-compose。使用
docker compose(中间空格)而非docker-compose。
配置镜像加速(参考 dongyubin/DockerHub):
cat > /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://docker.1panel.live",
"https://hub.rat.dev",
"https://dockerpull.org",
"https://dockerhub.icu"
]
}
EOF
systemctl daemon-reload && systemctl restart docker
2. 登录 harbor
docker login harbor.sino-assist.com
3. 在源机器停服并同步数据
# 停止容器
cd /opt/fastdfs && docker compose stop
# 后台同步数据到目标机器
nohup rsync -avz --progress /opt/fastdfs/ root@<目标IP>:/opt/fastdfs/ > /tmp/rsync_fastdfs.log 2>&1 &
# 查看同步进度
tail -f /tmp/rsync_fastdfs.log
# 确认同步完成
ps aux | grep rsync
tail -20 /tmp/rsync_fastdfs.log
4. 修改目标机器配置中的 IP
# 修改 storage.conf
sed -i 's/tracker_server=<源IP>:22122/tracker_server=<目标IP>:22122/' /opt/fastdfs/storage.conf
# 修改 docker-compose.yml
sed -i 's/<源IP>/<目标IP>/g' /opt/fastdfs/docker-compose.yml
5. 启动并验证
cd /opt/fastdfs && docker compose up -d
# 验证容器状态
docker ps
# 验证端口
ss -tlnp | grep -E '22122|23000|8088'
# 验证文件访问
curl http://<目标IP>:8088/group1/M00/<文件路径>
6. 修改上层应用配置
将应用中 FastDFS 地址从源机器 IP 改为目标机器 IP,端口不变。
数据未同步到 RabbitMQ
原因: 订阅的表正则表达式不匹配
解决:
# 检查 env_review 中的 canal_instance_filter_regex 配置
# 格式:schema.table,支持正则
# 示例:zd_rescue\\..* 订阅 zd_rescue 库下所有表
# 修改配置后重新部署
docker stack rm review_canal
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal
十六、部署 Elasticsearch + Kibana
15.1 服务说明
Elasticsearch 是分布式搜索和分析引擎,Kibana 是其可视化平台。本环境用于日志存储和分析。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_log_es |
| ES 镜像 | bitnami/elasticsearch:8.13.4 |
| Kibana 镜像 | bitnami/kibana:8.13.4 |
| ES HTTP 端口 | 9200 |
| ES Transport 端口 | 9300 |
| Kibana 端口 | 5601 |
| ES 堆内存 | 8192m |
| 部署节点 | 标签 review_es=1 的节点 |
15.2 部署前准备
添加节点标签
选择部署 ES 的节点(建议选择内存较大的节点):
# 将 ES 部署到 ZD-BAK-APP2
docker node update --label-add review_es=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
创建数据目录
在目标节点上创建数据目录并设置权限:
# 创建目录
mkdir -p /mnt/data/volumes/elasticsearch
mkdir -p /mnt/data/volumes/kibana/data
mkdir -p /mnt/data/volumes/kibana/config
# 设置权限(Bitnami 镜像使用 UID 1001)
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana
目录说明:
| 目录 | 用途 |
|---|---|
| /mnt/data/volumes/elasticsearch | ES 数据存储 |
| /mnt/data/volumes/kibana/data | Kibana 数据存储 |
| /mnt/data/volumes/kibana/config | Kibana 配置文件 |
调整系统参数
ES 需要调整 vm.max_map_count 参数:
# 临时生效
sysctl -w vm.max_map_count=262144
# 永久生效
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p
# 验证
sysctl vm.max_map_count
拉取镜像
方法一:直接从 Docker Hub 拉取
docker pull bitnami/elasticsearch:8.13.4
docker pull bitnami/kibana:8.13.4
方法二:通过私有仓库中转(推荐)
# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上操作
docker pull bitnami/elasticsearch:8.13.4
docker tag bitnami/elasticsearch:8.13.4 harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker push harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker pull bitnami/kibana:8.13.4
docker tag bitnami/kibana:8.13.4 harbor.sino-assist.com/bitnami/kibana:8.13.4
docker push harbor.sino-assist.com/bitnami/kibana:8.13.4
# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker tag harbor.sino-assist.com/bitnami/elasticsearch:8.13.4 bitnami/elasticsearch:8.13.4
docker pull harbor.sino-assist.com/bitnami/kibana:8.13.4
docker tag harbor.sino-assist.com/bitnami/kibana:8.13.4 bitnami/kibana:8.13.4
创建 Kibana 配置文件(可选)
如需自定义 Kibana 配置,创建配置文件:
cat > /mnt/data/volumes/kibana/config/kibana.yml << 'EOF'
i18n.locale: "zh-CN"
path:
data: /bitnami/kibana/data
pid:
file: /opt/bitnami/kibana/tmp/kibana.pid
server:
host: 0.0.0.0
port: 5601
elasticsearch:
hosts: http://review-es-elasticsearch:9200
EOF
chown 1001:1001 /mnt/data/volumes/kibana/config/kibana.yml
15.3 配置文件说明
env_review 配置项:
| 配置项 | 值 | 说明 |
|---|---|---|
| NAMESPACE | review | 命名空间,用于网络和主机名 |
| NODE_PORT | 9200 | ES HTTP 端口 |
| NODE_PORT_2 | 9300 | ES Transport 端口 |
| NODE_PORT_KIBANA | 5601 | Kibana Web 端口 |
docker-compose.yml 关键配置:
| 配置项 | 值 | 说明 |
|---|---|---|
| ELASTICSEARCH_HEAP_SIZE | 8192m | ES JVM 堆内存大小 |
| hostname | ${NAMESPACE}-es-elasticsearch | ES 容器主机名 |
| KIBANA_ELASTICSEARCH_URL | ${NAMESPACE}-es-elasticsearch | Kibana 连接的 ES 地址 |
15.3.1 安装 IK 分词器(可选)
IK 分词器是 Elasticsearch 的中文分词插件,版本必须与 ES 版本完全匹配。
创建插件目录
在 ES 部署节点上执行:
# 创建 plugins 目录
mkdir -p /mnt/data/volumes/elasticsearch-plugins
# 设置权限(Bitnami 镜像使用 UID 1001)
chown -R 1001:1001 /mnt/data/volumes/elasticsearch-plugins
下载并安装 IK 分词器
cd /mnt/data/volumes/elasticsearch-plugins
# 下载 IK 分词器(版本 8.13.4 对应 ES 8.13.4)
curl -L -o analysis-ik.zip https://get.infini.cloud/elasticsearch/analysis-ik/8.13.4
# 解压到 analysis-ik 目录(使用 Python)
python3 -c "import zipfile; zipfile.ZipFile('analysis-ik.zip').extractall('analysis-ik')"
# 删除压缩包
rm -f analysis-ik.zip
# 设置权限
chown -R 1001:1001 analysis-ik
注意:IK 分词器版本必须与 Elasticsearch 版本完全一致。如果 ES 升级,需要同步更新 IK 分词器版本。
验证 IK 分词器安装
部署完成后,验证插件是否加载:
# 查看已安装的插件
curl http://<ES节点IP>:9200/_cat/plugins?v
# 预期输出应包含 analysis-ik
# 测试 IK 分词器(ik_max_word 细粒度分词)
curl -X POST "http://<ES节点IP>:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
"analyzer": "ik_max_word",
"text": "中华人民共和国国歌"
}'
# 测试 IK 分词器(ik_smart 粗粒度分词)
curl -X POST "http://<ES节点IP>:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
"analyzer": "ik_smart",
"text": "中华人民共和国国歌"
}'
IK 分词模式说明:
| 分词器 | 说明 | 示例 |
|---|---|---|
| ik_max_word | 细粒度分词,穷尽所有可能的组合 | "中华人民共和国" → 中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、共和、国 |
| ik_smart | 粗粒度分词,做最少切分 | "中华人民共和国" → 中华人民共和国 |
15.4 部署命令
cd /path/to/docker-swarm-review/elasticsearch
# 转换 Windows 换行符(如果文件从 Windows 传输)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es
15.5 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_log_es
# 预期输出:
# review_log_es_elasticsearch replicated 1/1
# review_log_es_kibana replicated 1/1
# 查看详细状态
docker stack ps review_log_es --no-trunc | grep -v Shutdown
测试 ES 连通性
注意:
localhost只能在 ES 实际运行的节点上使用。推荐使用节点 IP 或容器内测试。
# 方法1:通过节点 IP 测试(推荐,任意位置可执行)
# 将 <ES节点IP> 替换为 ES 实际运行节点的 IP
curl http://<ES节点IP>:9200
# 方法2:通过容器内部测试(在 ES 所在节点执行)
docker exec $(docker ps -q -f name=review_log_es_elasticsearch) curl -s http://localhost:9200
# 方法3:在 ES 所在节点上使用 localhost
curl http://localhost:9200
# 预期输出(JSON 格式):
# {
# "name" : "review-es-elasticsearch",
# "cluster_name" : "elasticsearch",
# "cluster_uuid" : "xxx",
# "version" : {
# "number" : "8.13.4",
# ...
# },
# "tagline" : "You Know, for Search"
# }
# 检查集群健康状态
curl http://<ES节点IP>:9200/_cluster/health?pretty
# 检查节点信息
curl http://<ES节点IP>:9200/_cat/nodes?v
确认 ES 运行节点
# 查看 ES 实际运行在哪个节点
docker service ps review_log_es_elasticsearch --format "table {{.Node}}\t{{.CurrentState}}"
测试 Kibana 连通性
注意:同 ES,
localhost只能在 Kibana 实际运行的节点上使用。
# 方法1:通过节点 IP 测试(推荐)
curl http://<Kibana节点IP>:5601/api/status
# 方法2:通过容器内部测试
docker exec $(docker ps -q -f name=review_log_es_kibana) curl -s http://localhost:5601/api/status
# 浏览器访问
# http://<节点IP>:5601
15.6 连接信息
| 服务 | 地址 | 说明 |
|---|---|---|
| Elasticsearch HTTP | http://<节点IP>:9200 | REST API 接口 |
| Elasticsearch Transport | <节点IP>:9300 | 节点间通信 |
| Kibana | http://<节点IP>:5601 | Web 可视化界面 |
| ES 内部地址 | review-es-elasticsearch:9200 | overlay 网络内访问 |
15.7 常用运维命令
# 查看 ES 服务日志
docker service logs review_log_es_elasticsearch --tail 100
# 查看 Kibana 服务日志
docker service logs review_log_es_kibana --tail 100
# 强制重启 ES 服务
docker service update --force review_log_es_elasticsearch
# 强制重启 Kibana 服务
docker service update --force review_log_es_kibana
# 进入 ES 容器
docker exec -it $(docker ps -q -f name=review_log_es_elasticsearch) bash
# 进入 Kibana 容器
docker exec -it $(docker ps -q -f name=review_log_es_kibana) bash
# 删除并重新部署
docker stack rm review_log_es
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es
15.8 ES 常用 API
# 查看所有索引
curl http://localhost:9200/_cat/indices?v
# 查看集群健康状态
curl http://localhost:9200/_cluster/health?pretty
# 查看节点状态
curl http://localhost:9200/_cat/nodes?v
# 查看分片分配
curl http://localhost:9200/_cat/shards?v
# 查看集群设置
curl http://localhost:9200/_cluster/settings?pretty
# 创建索引
curl -X PUT "http://localhost:9200/test_index"
# 删除索引
curl -X DELETE "http://localhost:9200/test_index"
# 查看索引映射
curl http://localhost:9200/test_index/_mapping?pretty
15.9 常见问题及解决
15.9.1 ES 启动失败:max virtual memory areas vm.max_map_count is too low
问题: ES 日志显示 max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
原因: 系统 vm.max_map_count 参数默认值过低
解决:
# 临时生效
sysctl -w vm.max_map_count=262144
# 永久生效
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p
# 重启 ES 服务
docker service update --force review_log_es_elasticsearch
15.9.2 ES 启动失败:Permission denied
问题: ES 无法写入数据目录,日志显示权限错误
原因: 数据目录权限不正确,Bitnami 镜像使用 UID 1001 运行
解决:
# 修正目录权限
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chmod 755 /mnt/data/volumes/elasticsearch
# 重启 ES 服务
docker service update --force review_log_es_elasticsearch
15.9.3 ES 启动失败:Disk usage exceeded flood-stage watermark
问题: ES 拒绝写入,日志显示磁盘空间不足
原因: 磁盘使用率超过 95%(flood stage watermark)
解决:
# 检查磁盘使用
df -h /mnt/data/volumes/elasticsearch
# 清理旧数据或扩容磁盘
# 临时解除只读限制(不推荐生产环境使用)
curl -X PUT "http://localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
"persistent": {
"cluster.routing.allocation.disk.watermark.flood_stage": "98%"
}
}'
# 解除索引只读状态
curl -X PUT "http://localhost:9200/_all/_settings" -H 'Content-Type: application/json' -d'
{
"index.blocks.read_only_allow_delete": null
}'
15.9.4 curl localhost:9200 无响应,但 IP 可以访问
问题: 在服务器上执行 curl http://localhost:9200 无响应,但 curl http://<节点IP>:9200 可以
原因: localhost 只指向当前机器。如果你在 A 节点执行 curl,但 ES 运行在 B 节点,localhost 无法访问
解决:
# 1. 确认 ES 运行在哪个节点
docker service ps review_log_es_elasticsearch --format "table {{.Node}}\t{{.CurrentState}}"
# 2. 使用正确的访问方式:
# - 在 ES 所在节点:可以用 localhost
# - 在其他节点:必须用 ES 节点的 IP
curl http://<ES节点IP>:9200
# 3. 或通过容器内部测试(在 ES 所在节点执行)
docker exec $(docker ps -q -f name=review_log_es_elasticsearch) curl -s http://localhost:9200
说明: 只要通过 IP 能访问,ES 部署就是成功的。这是正常现象,不是故障。
15.9.5 Kibana 启动失败:Unable to retrieve version information from Elasticsearch
问题: Kibana 无法连接 ES
原因: ES 尚未启动完成,或网络不通
解决:
# 检查 ES 是否正常运行(使用节点 IP)
curl http://<ES节点IP>:9200
# 检查网络连通性(在 Kibana 容器内)
docker exec $(docker ps -q -f name=review_log_es_kibana) curl review-es-elasticsearch:9200
# 如果 ES 正常但 Kibana 无法连接,检查 overlay 网络
docker network inspect review
# 重启 Kibana 服务
docker service update --force review_log_es_kibana
15.9.6 Kibana 启动失败:FATAL Error: Port 5601 is already in use
问题: Kibana 端口被占用
原因: 端口被其他进程占用
解决:
# 检查端口占用
netstat -tlnp | grep 5601
# 或
ss -tlnp | grep 5601
# 停止占用端口的进程,或修改 env_review 中的端口配置
# NODE_PORT_KIBANA=5602
# 重新部署
docker stack rm review_log_es
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es
15.9.7 ES 集群状态为 Yellow
问题: curl http://localhost:9200/_cluster/health 返回 status: yellow
原因: 单节点部署时,副本分片无法分配(正常现象)
解决:
单节点环境下,yellow 状态是正常的。如需变为 green:
# 将所有索引的副本数设为 0
curl -X PUT "http://localhost:9200/_settings" -H 'Content-Type: application/json' -d'
{
"index": {
"number_of_replicas": 0
}
}'
15.9.8 ES 内存不足 (OOM)
问题: ES 进程被 OOM Killer 杀死
原因: 堆内存设置过大,超过系统可用内存
解决:
# 检查系统内存
free -h
# 修改 docker-compose.yml 中的堆内存设置
# ELASTICSEARCH_HEAP_SIZE=4096m # 根据实际情况调整
# 重新部署
docker stack rm review_log_es
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es
堆内存建议:
- 堆内存不超过物理内存的 50%
- 堆内存不超过 32GB(JVM 压缩指针限制)
- 为操作系统和文件缓存预留足够内存
15.9.9 无法拉取镜像
问题: dial tcp xxx:443: i/o timeout 或 no such image
原因: 无法访问 Docker Hub
解决: 参考 9.1 节「镜像拉取失败」,通过私有仓库中转
# 在能访问外网的服务器上
docker pull bitnami/elasticsearch:8.13.4
docker tag bitnami/elasticsearch:8.13.4 harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker push harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
# 在目标服务器上
docker pull harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker tag harbor.sino-assist.com/bitnami/elasticsearch:8.13.4 bitnami/elasticsearch:8.13.4
15.9.10 Kibana 界面显示英文
问题: Kibana 界面为英文
原因: 未配置中文语言
解决:
创建或修改 Kibana 配置文件:
# 添加中文配置
echo 'i18n.locale: "zh-CN"' >> /mnt/data/volumes/kibana/config/kibana.yml
# 重启 Kibana
docker service update --force review_log_es_kibana
十七、部署 Log 服务(Logstash + Filebeat)
16.1 服务说明
Log 服务用于收集、处理和存储应用日志,由 Filebeat(日志收集)和 Logstash(日志处理)组成。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_log |
| Logstash 镜像 | docker.elastic.co/logstash/logstash:8.13.4 |
| Filebeat 镜像 | docker.elastic.co/beats/filebeat:8.13.4 |
| Logstash 端口 | 5044 |
| 日志卷 | review_logs |
| 依赖 | Elasticsearch |
16.2 架构说明
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Filebeat │────▶│ Logstash │────▶│ Elasticsearch│
│ (每个节点) │ │ (ES 节点) │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
收集日志 过滤/转换 存储/索引
/logs/*/*.log
组件说明:
| 组件 | 部署模式 | 说明 |
|---|---|---|
| Filebeat | global(每个节点一个) | 轻量级日志收集器,监控 /logs 目录 |
| Logstash | replicated(部署在 ES 节点) | 日志处理管道,解析、过滤、转换日志 |
16.3 部署前准备
创建日志卷
# 创建外部卷用于收集应用日志
docker volume create review_logs
# 验证
docker volume ls | grep review_logs
重要:此卷需要在所有节点上创建,用于应用服务挂载日志目录。
拉取镜像
方法一:直接从 Elastic 官方拉取
# 在所有节点上执行(因为 Filebeat 是全局部署)
docker pull docker.elastic.co/logstash/logstash:8.13.4
docker pull docker.elastic.co/beats/filebeat:8.13.4
方法二:通过私有仓库中转(推荐)
# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull docker.elastic.co/logstash/logstash:8.13.4
docker tag docker.elastic.co/logstash/logstash:8.13.4 harbor.sino-assist.com/elastic/logstash:8.13.4
docker push harbor.sino-assist.com/elastic/logstash:8.13.4
docker pull docker.elastic.co/beats/filebeat:8.13.4
docker tag docker.elastic.co/beats/filebeat:8.13.4 harbor.sino-assist.com/elastic/filebeat:8.13.4
docker push harbor.sino-assist.com/elastic/filebeat:8.13.4
# 2. 在所有目标节点上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/elastic/logstash:8.13.4
docker tag harbor.sino-assist.com/elastic/logstash:8.13.4 docker.elastic.co/logstash/logstash:8.13.4
docker pull harbor.sino-assist.com/elastic/filebeat:8.13.4
docker tag harbor.sino-assist.com/elastic/filebeat:8.13.4 docker.elastic.co/beats/filebeat:8.13.4
注意:Filebeat 是全局部署(每个节点一个),所以需要在所有 Swarm 节点上拉取 Filebeat 镜像。
16.4 配置文件说明
env_review 配置项
| 配置项 | 值 | 说明 |
|---|---|---|
| NAMESPACE | review | 命名空间 |
| NODE_PORT | 5044 | Logstash 监听端口 |
logstash.conf 说明
filter {
grok {
# 解析日志格式:时间戳 [服务名] [TID:链路ID] [线程] 日志级别 类名 - 消息
match => { "message" => "..." }
}
date {
# 将日志时间戳设置为 @timestamp
}
mutate {
# 清理多余字段
}
}
output {
elasticsearch {
hosts => [ "review-es-elasticsearch:9200" ]
index => "sslog-%{[service]}" # 按服务名创建索引
}
}
filebeat.yml 说明
| 配置项 | 值 | 说明 |
|---|---|---|
| paths | /logs//.log | 监控的日志文件路径 |
| multiline.pattern | ^[0-9]{4}-[0-9]{2}-[0-9]{2} | 多行日志合并(以日期开头为新日志) |
| output.logstash.hosts | review-log-logstash:5044 | Logstash 地址 |
16.5 部署命令
cd /path/to/docker-swarm-review/log
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
sed -i 's/\r$//' logstash.conf
sed -i 's/\r$//' filebeat.yml
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log
16.6 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_log
# 预期输出:
# review_log_logstash replicated 1/1
# review_log_filebeat global 3/3 (取决于节点数)
# 查看详细状态
docker stack ps review_log --no-trunc | grep -v Shutdown
查看服务日志
# 查看 Logstash 日志
docker service logs review_log_logstash --tail 50
# 查看 Filebeat 日志
docker service logs review_log_filebeat --tail 50
验证日志流转
# 检查 ES 中是否有日志索引
curl http://<ES节点IP>:9200/_cat/indices?v | grep sslog
# 预期输出(有应用日志时):
# yellow open sslog-service1 xxx 1 1 100 0 50kb 50kb
16.7 连接信息
| 服务 | 地址 | 说明 |
|---|---|---|
| Logstash | review-log-logstash:5044 | overlay 网络内部地址 |
| 日志卷 | review_logs:/logs | 应用日志挂载点 |
| ES 索引 | sslog-{service} | 日志索引命名规则 |
16.8 应用接入说明
业务应用需要将日志输出到 review_logs 卷中,才能被 Filebeat 收集。
应用 docker-compose.yml 示例
services:
my-service:
image: my-app:latest
volumes:
- review_logs:/logs
# 应用日志输出到 /logs/my-service/xxx.log
volumes:
review_logs:
external: true
日志格式要求
为了 Logstash 正确解析,应用日志应遵循以下格式:
2024-01-01 12:00:00.000 [service-name] [TID:trace-id] [thread-name] INFO com.example.Class - Log message
16.9 常用运维命令
# 查看 Logstash 日志
docker service logs review_log_logstash --tail 100
# 查看 Filebeat 日志
docker service logs review_log_filebeat --tail 100
# 进入 Logstash 容器
docker exec -it $(docker ps -q -f name=review_log_logstash) bash
# 进入 Filebeat 容器(指定节点)
docker exec -it $(docker ps -q -f name=review_log_filebeat) bash
# 强制重启 Logstash
docker service update --force review_log_logstash
# 强制重启 Filebeat(所有节点)
docker service update --force review_log_filebeat
# 删除并重新部署
docker stack rm review_log
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log
16.10 日志流转详解
完整数据流向图
┌──────────────────────────────────────────────────────────────────────────────┐
│ Docker Swarm 集群 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Docker Volume: review_logs │ │
│ │ │ │
│ │ /logs/sa-gateway/sa-gateway.log │ │
│ │ /logs/sa-auth/sa-auth.log │ │
│ │ /logs/sa-system/sa-system.log │ │
│ │ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ │ 读取 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Filebeat (每节点一个) │ │
│ │ │ │
│ │ - 监控 /logs/*/*.log │ │
│ │ - 多行日志合并(异常堆栈等) │ │
│ │ - 发送到 Logstash:5044 │ │
│ │ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ │ TCP 5044 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Logstash │ │
│ │ │ │
│ │ - Grok 解析日志格式 │ │
│ │ - 提取字段: service, tid, thread, loglevel, class, message │ │
│ │ - 时间戳转换 │ │
│ │ - 输出到 Elasticsearch │ │
│ │ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ │ HTTP 9200 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Elasticsearch │ │
│ │ │ │
│ │ 索引: │ │
│ │ - sslog-sa-gateway │ │
│ │ - sslog-sa-auth │ │
│ │ - sslog-sa-system │ │
│ │ - sslog-default(解析失败的日志) │ │
│ │ │ │
│ └───────────────────────────────┬─────────────────────────────────────────┘ │
│ │ │
│ │ HTTP 5601 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Kibana │ │
│ │ │ │
│ │ - 可视化查询 │ │
│ │ - 日志检索 │ │
│ │ - 仪表盘 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Grok 解析示例
原始日志:
2025-12-23 10:30:45.123 [sa-gateway] [TID:abc123def456] [http-nio-8080-exec-1] INFO c.s.g.filter.AuthFilter - 用户登录成功
Grok 表达式:
%{TIMESTAMP_ISO8601:oldtimestamp}\s+\[%{DATA:service}\]\s+\[TID:%{NOTSPACE:tid}\]\s+\[%{DATA:thread}\]\s+%{LOGLEVEL:loglevel}\s+%{NOTSPACE:class}\s+-%{GREEDYDATA:oldmessage}
解析后的字段:
| 字段 | 值 | 说明 |
|---|---|---|
@timestamp |
2025-12-23T10:30:45.123+08:00 | 日志时间(转换后) |
service |
sa-gateway | 服务名(用于创建索引) |
tid |
abc123def456 | SkyWalking Trace ID |
thread |
http-nio-8080-exec-1 | 线程名 |
loglevel |
INFO | 日志级别 |
class |
c.s.g.filter.AuthFilter | 类名 |
message |
用户登录成功 | 日志内容 |
SkyWalking Trace ID 关联
日志中的 TID 字段就是 SkyWalking 的 Trace ID,由 SkyWalking Logback 插件自动注入。
配置要求: 应用需要引入 SkyWalking Logback 依赖:
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
</dependency>
关联查询:
- 在 Kibana 中根据
tid字段搜索特定请求的所有日志 - 在 SkyWalking UI 中根据 Trace ID 查看完整调用链
- 实现日志与链路追踪的关联分析
16.11 Kibana 日志查询
访问地址
http://192.168.3.132:5601
索引模式配置
- 进入 Management → Stack Management → Index Patterns
- 创建索引模式:
sslog-* - 选择时间字段:
@timestamp
常用查询语法
# 查询某个服务的日志
service: "sa-gateway"
# 查询某个 Trace ID 的所有日志(跨服务追踪)
tid: "abc123def456"
# 查询错误日志
loglevel: "ERROR"
# 查询某个服务的错误日志
service: "sa-gateway" AND loglevel: "ERROR"
# 查询包含特定关键词的日志
message: "登录失败"
# 查询某个时间范围内的错误
loglevel: "ERROR" AND @timestamp >= "2025-12-23T10:00:00"
# 排除某些日志
service: "sa-gateway" AND NOT message: "健康检查"
日志级别查询
| 级别 | 查询语法 | 说明 |
|---|---|---|
| ERROR | loglevel: "ERROR" |
错误日志 |
| WARN | loglevel: "WARN" |
警告日志 |
| INFO | loglevel: "INFO" |
信息日志 |
| DEBUG | loglevel: "DEBUG" |
调试日志 |
16.12 常见问题及解决
16.12.1 Filebeat 启动失败:No such volume
问题: Filebeat 无法启动,提示找不到卷
原因: review_logs 卷未创建
解决:
# 创建卷
docker volume create review_logs
# 重启服务
docker service update --force review_log_filebeat
16.10.2 Logstash 无法连接 Elasticsearch
问题: Logstash 日志显示无法连接 ES
原因: ES 服务未启动或网络不通
解决:
# 检查 ES 是否正常运行
curl http://<ES节点IP>:9200
# 检查网络连通性(在 Logstash 容器内)
docker exec $(docker ps -q -f name=review_log_logstash) curl review-es-elasticsearch:9200
# 确保 Logstash 和 ES 在同一 overlay 网络
docker network inspect review
# 重启 Logstash
docker service update --force review_log_logstash
16.10.3 日志未出现在 ES 中
问题: 应用日志未被收集到 ES
原因:
- 应用未挂载 review_logs 卷
- 日志路径不匹配 /logs//.log
- 日志格式不符合 Logstash grok 规则
解决:
# 1. 检查日志卷是否有内容
docker run --rm -v review_logs:/logs alpine ls -la /logs
# 2. 检查 Filebeat 是否在收集
docker service logs review_log_filebeat --tail 50 | grep -i "harvest"
# 3. 检查 Logstash 是否在处理
docker service logs review_log_logstash --tail 50 | grep -i "event"
16.10.4 Filebeat 镜像拉取失败
问题: 某些节点上 Filebeat 启动失败,提示 No such image
原因: Filebeat 是全局部署,需要所有节点都有镜像
解决:
# 在缺少镜像的节点上拉取
docker pull harbor.sino-assist.com/elastic/filebeat:8.13.4
docker tag harbor.sino-assist.com/elastic/filebeat:8.13.4 docker.elastic.co/beats/filebeat:8.13.4
# 重启服务
docker service update --force review_log_filebeat
16.10.5 Logstash grok 解析失败
问题: 日志写入 ES 但字段未正确解析
原因: 日志格式与 grok 规则不匹配
解决:
# 查看 Logstash 日志中的 grok 解析错误
docker service logs review_log_logstash --tail 100 | grep -i "grok"
# 进入容器测试 grok 规则
docker exec -it $(docker ps -q -f name=review_log_logstash) bash
# 修改 logstash.conf 后重新部署
docker stack rm review_log
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log
16.10.6 磁盘空间不足
问题: 日志卷占用大量磁盘空间
原因: 日志文件未定期清理
解决:
# 检查卷使用情况
docker system df -v | grep review_logs
# 清理旧日志(在应用层面配置日志轮转)
# 或手动清理
docker run --rm -v review_logs:/logs alpine find /logs -name "*.log" -mtime +7 -delete
十八、部署 SkyWalking
17.1 服务说明
SkyWalking 是一款开源的 APM(应用性能监控)系统,用于分布式系统的链路追踪、性能指标分析和服务拓扑可视化。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_skywalking |
| OAP 镜像 | apache/skywalking-oap-server:10.0.0 |
| UI 镜像 | apache/skywalking-ui:10.0.0 |
| gRPC 端口 | 11800 (Agent 上报) |
| HTTP 端口 | 12800 (OAP REST API) |
| UI 端口 | 18080 |
| 存储 | Elasticsearch |
| JVM 内存 | 2048m |
| 依赖 | Elasticsearch |
17.2 架构说明
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Java Agent │────▶│ OAP Server │────▶│ Elasticsearch│
│ (应用服务) │ │ :11800 │ │ :9200 │
└──────────────┘ └──────┬───────┘ └──────────────┘
│
┌─────▼─────┐
│ UI │
│ :18080 │
└───────────┘
组件说明:
| 组件 | 端口 | 说明 |
|---|---|---|
| OAP Server | 11800 (gRPC), 12800 (HTTP) | 数据收集、分析、存储 |
| UI | 18080 | Web 可视化界面 |
17.3 部署前准备
添加节点标签
# 将 SkyWalking 部署到 ZD-BAK-APP2(建议与 ES 同节点)
docker node update --label-add review_skywalking=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
在 ES 中创建索引生命周期策略(可选但推荐)
SkyWalking 会产生大量索引数据,建议配置生命周期策略自动清理旧数据:
# 创建 sw-policy 索引生命周期策略(保留 7 天数据)
curl -X PUT "http://<ES节点IP>:9200/_ilm/policy/sw-policy" -H 'Content-Type: application/json' -d'
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_age": "1d",
"max_primary_shard_size": "50gb"
}
}
},
"delete": {
"min_age": "7d",
"actions": {
"delete": {}
}
}
}
}
}'
# 验证策略
curl "http://<ES节点IP>:9200/_ilm/policy/sw-policy?pretty"
拉取镜像
方法一:直接拉取
docker pull apache/skywalking-oap-server:10.0.0
docker pull apache/skywalking-ui:10.0.0
方法二:通过私有仓库中转(推荐)
# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull apache/skywalking-oap-server:10.0.0
docker tag apache/skywalking-oap-server:10.0.0 harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0
docker push harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0
docker pull apache/skywalking-ui:10.0.0
docker tag apache/skywalking-ui:10.0.0 harbor.sino-assist.com/apache/skywalking-ui:10.0.0
docker push harbor.sino-assist.com/apache/skywalking-ui:10.0.0
# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0
docker tag harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0 apache/skywalking-oap-server:10.0.0
docker pull harbor.sino-assist.com/apache/skywalking-ui:10.0.0
docker tag harbor.sino-assist.com/apache/skywalking-ui:10.0.0 apache/skywalking-ui:10.0.0
17.4 配置文件说明
env_review 配置项
| 配置项 | 值 | 说明 |
|---|---|---|
| NAMESPACE | review | 命名空间 |
| NODE_PORT | 11800 | OAP gRPC 端口(Agent 上报) |
| NODE_PORT_2 | 12800 | OAP HTTP 端口(REST API) |
| NODE_PORT_UI | 18080 | UI Web 端口 |
docker-compose.yml 关键配置
| 配置项 | 值 | 说明 |
|---|---|---|
| SW_STORAGE | elasticsearch | 存储类型 |
| SW_STORAGE_ES_CLUSTER_NODES | review-es-elasticsearch:9200 | ES 集群地址 |
| SW_HEALTH_CHECKER | default | 健康检查器 |
| SW_TELEMETRY | prometheus | 遥测类型 |
| SW_STORAGE_ES_ADVANCED | {"index.lifecycle.name":"sw-policy"} | ES 索引生命周期策略 |
| JAVA_OPTS | -Xms2048m -Xmx2048m | JVM 内存设置 |
17.5 部署命令
cd /path/to/docker-swarm-review/skywalking
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking
17.6 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_skywalking
# 预期输出:
# review_skywalking_oap replicated 1/1
# review_skywalking_ui replicated 1/1
# 查看详细状态
docker stack ps review_skywalking --no-trunc | grep -v Shutdown
查看服务日志
# 查看 OAP 日志
docker service logs review_skywalking_oap --tail 100
# 查看 UI 日志
docker service logs review_skywalking_ui --tail 50
# 确认 OAP 启动成功(查找 started 关键字)
docker service logs review_skywalking_oap --tail 100 | grep -i "started"
访问 UI
在浏览器访问:
http://<节点IP>:18080
如果 UI 能正常打开并显示界面,说明 SkyWalking 部署成功。
17.7 连接信息
| 服务 | 地址 | 说明 |
|---|---|---|
| OAP gRPC | <节点IP>:11800 | Agent 上报地址 |
| OAP HTTP | <节点IP>:12800 | REST API / GraphQL |
| UI | http://<节点IP>:18080 | Web 界面 |
| 内部 OAP gRPC | review-skywalking-oap:11800 | overlay 网络内访问 |
| 内部 OAP HTTP | review-skywalking-oap:12800 | overlay 网络内访问 |
17.8 应用接入说明
业务应用需要集成 SkyWalking Agent 才能上报链路数据。
Java 应用接入示例
- 下载 SkyWalking Agent:
wget https://archive.apache.org/dist/skywalking/java-agent/9.0.0/apache-skywalking-java-agent-9.0.0.tgz
tar -zxvf apache-skywalking-java-agent-9.0.0.tgz
- 启动应用时添加 Agent 参数:
java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=your-service-name \
-Dskywalking.collector.backend_service=<节点IP>:11800 \
-jar your-app.jar
Docker 应用接入示例
services:
my-service:
image: my-app:latest
environment:
- JAVA_TOOL_OPTIONS=-javaagent:/skywalking-agent/skywalking-agent.jar
- SW_AGENT_NAME=my-service
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=review-skywalking-oap:11800
volumes:
- /path/to/skywalking-agent:/skywalking-agent
17.9 常用运维命令
# 查看 OAP 日志
docker service logs review_skywalking_oap --tail 100
# 查看 UI 日志
docker service logs review_skywalking_ui --tail 100
# 进入 OAP 容器
docker exec -it $(docker ps -q -f name=review_skywalking_oap) bash
# 进入 UI 容器
docker exec -it $(docker ps -q -f name=review_skywalking_ui) bash
# 强制重启 OAP
docker service update --force review_skywalking_oap
# 强制重启 UI
docker service update --force review_skywalking_ui
# 删除并重新部署
docker stack rm review_skywalking
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking
17.10 常见问题及解决
17.10.1 OAP 启动失败:无法连接 Elasticsearch
问题: OAP 日志显示无法连接 ES
原因: ES 服务未启动或网络不通
解决:
# 检查 ES 是否正常运行
curl http://<ES节点IP>:9200
# 检查网络连通性(在 OAP 容器内)
docker exec $(docker ps -q -f name=review_skywalking_oap) curl review-es-elasticsearch:9200
# 确保 OAP 和 ES 在同一 overlay 网络
docker network inspect review
# 重启 OAP
docker service update --force review_skywalking_oap
17.10.2 UI 启动失败:无法连接 OAP
问题: UI 无法显示数据或无法访问
原因: OAP 尚未启动完成
解决:
# 确认 OAP 已启动
docker service logs review_skywalking_oap --tail 50 | grep -i "started"
# 重启 UI
docker service update --force review_skywalking_ui
17.10.3 OAP 内存不足 (OOM)
问题: OAP 进程被 OOM Killer 杀死
原因: JVM 内存设置过大或数据量过多
解决:
# 检查系统内存
free -h
# 修改 docker-compose.yml 中的 JAVA_OPTS
# JAVA_OPTS=-Xms1024m -Xmx1024m # 根据实际情况调整
# 重新部署
docker stack rm review_skywalking
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking
17.10.4 ES 索引占用过多磁盘空间
问题: SkyWalking 索引数据增长过快
原因: 未配置索引生命周期策略
解决:
# 1. 创建索引生命周期策略(参考 17.3 节)
# 2. 手动删除旧索引
curl -X DELETE "http://<ES节点IP>:9200/sw_*_20241201*"
# 3. 查看 SkyWalking 索引
curl "http://<ES节点IP>:9200/_cat/indices?v" | grep sw_
17.10.5 Agent 无法连接 OAP
问题: 应用接入 Agent 后无数据上报
原因:
- OAP 地址配置错误
- 网络不通(Agent 在容器外,需要用宿主机 IP)
- 端口未开放
解决:
# 1. 确认 Agent 配置的 OAP 地址
# 如果 Agent 在容器外,使用宿主机 IP:11800
# 如果 Agent 在同一 overlay 网络,使用 review-skywalking-oap:11800
# 2. 测试网络连通性
telnet <OAP地址> 11800
# 3. 检查 OAP gRPC 端口是否监听
netstat -tlnp | grep 11800
17.10.6 UI 显示无服务/无数据
问题: UI 可以访问但没有显示任何服务或数据
原因:
- 尚未有应用接入 Agent
- Agent 配置错误
- 时间范围选择不对
解决:
# 1. 检查 OAP 是否收到数据
docker service logs review_skywalking_oap --tail 100 | grep -i "register"
# 2. 在 UI 中调整时间范围(右上角)
# 3. 确认应用已正确配置 Agent
十九、部署 MongoDB
18.1 服务说明
MongoDB 是一款开源的 NoSQL 文档数据库,本环境采用单机模式部署。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_mongodb |
| 镜像 | bitnami/mongodb:6.0 |
| 端口 | 27017 |
| Root 用户 | root |
| Root 密码 | 123456 |
| 默认数据库 | gps_data |
| 部署节点 | 标签 review_mongodb=1 的节点 |
18.2 部署前准备
添加节点标签
# 将 MongoDB 部署到 ZD-BAK-APP2
docker node update --label-add review_mongodb=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
创建数据目录
# 在目标节点创建数据目录
mkdir -p /mnt/data/volumes/mongodb
# 设置权限(Bitnami 镜像使用 UID 1001)
chown -R 1001:1001 /mnt/data/volumes/mongodb
拉取镜像
方法一:直接拉取
docker pull bitnami/mongodb:6.0
方法二:通过私有仓库中转(推荐)
# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull bitnami/mongodb:6.0
docker tag bitnami/mongodb:6.0 harbor.sino-assist.com/bitnami/mongodb:6.0
docker push harbor.sino-assist.com/bitnami/mongodb:6.0
# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/mongodb:6.0
docker tag harbor.sino-assist.com/bitnami/mongodb:6.0 bitnami/mongodb:6.0
18.3 配置文件说明
env_review 配置项
| 配置项 | 值 | 说明 |
|---|---|---|
| NAMESPACE | review | 命名空间 |
| NODE_PORT | 27017 | MongoDB 端口 |
| MONGODB_DATABASE | gps_data | 默认创建的数据库 |
docker-compose.yml 关键配置
| 配置项 | 值 | 说明 |
|---|---|---|
| MONGODB_ROOT_USER | root | 管理员用户名 |
| MONGODB_ROOT_PASSWORD | 123456 | 管理员密码 |
| MONGODB_DATABASE | ${MONGODB_DATABASE} | 默认数据库(来自 env 文件) |
| volumes | /mnt/data/volumes/mongodb:/bitnami/mongodb | 数据持久化目录 |
18.4 部署命令
cd /opt/swarm/support/mongodb
# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb
18.5 验证部署
查看服务状态
# 查看服务列表
docker service ls | grep review_mongodb
# 预期输出:
# review_mongodb_db replicated 1/1
# 查看详细状态
docker stack ps review_mongodb --no-trunc | grep -v Shutdown
测试连接
# 方法1:通过容器内部测试
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "db.adminCommand('ping')"
# 预期输出:{ ok: 1 }
# 方法2:查看数据库列表
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "show dbs"
验证挂载目录
# 查看容器挂载信息
docker inspect $(docker ps -q -f name=review_mongodb) --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
# 预期输出:/mnt/data/volumes/mongodb -> /bitnami/mongodb
# 检查目录是否有数据
ls -la /mnt/data/volumes/mongodb/
18.6 连接信息
| 项目 | 值 |
|---|---|
| 连接地址 | mongodb://root:123456@<节点IP>:27017 |
| 内部地址 | mongodb://root:123456@review-mongodb-db:27017 |
| Root 用户 | root |
| Root 密码 | 123456 |
| 默认数据库 | gps_data |
18.7 常用运维命令
# 查看服务日志
docker service logs review_mongodb_db --tail 100
# 进入 MongoDB 容器
docker exec -it $(docker ps -q -f name=review_mongodb_db) bash
# 进入 MongoDB Shell
docker exec -it $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456
# 强制重启服务
docker service update --force review_mongodb_db
# 删除并重新部署
docker stack rm review_mongodb
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb
18.8 MongoDB 常用命令
# 进入 MongoDB Shell 后执行
# 查看所有数据库
show dbs
# 切换数据库
use gps_data
# 查看当前数据库的集合
show collections
# 创建集合
db.createCollection("test_collection")
# 插入文档
db.test_collection.insertOne({ name: "test", value: 123 })
# 查询文档
db.test_collection.find()
# 创建用户
db.createUser({
user: "app_user",
pwd: "app_password",
roles: [{ role: "readWrite", db: "gps_data" }]
})
# 查看用户
db.getUsers()
18.9 常见问题及解决
18.9.1 MongoDB 启动失败:Permission denied
问题: MongoDB 无法写入数据目录
原因: 数据卷权限不正确,Bitnami 镜像使用 UID 1001 运行
解决:
# 如果使用本地目录挂载,设置权限
chown -R 1001:1001 /path/to/mongodb/data
# 重启服务
docker service update --force review_mongodb_db
18.9.2 无法连接 MongoDB
问题: 应用无法连接 MongoDB
原因:
- 网络不通
- 认证信息错误
- 端口未开放
解决:
# 1. 确认服务运行正常
docker service ls | grep review_mongodb
# 2. 确认端口监听(在 MongoDB 所在节点)
netstat -tlnp | grep 27017
# 3. 测试连接
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "db.adminCommand('ping')"
# 4. 检查网络(从其他容器)
docker run --rm --network review bitnami/mongodb:6.0 mongosh mongodb://root:123456@review-mongodb-db:27017 --eval "db.adminCommand('ping')"
18.9.3 认证失败
问题: Authentication failed
原因: 用户名或密码错误
解决:
# 确认连接字符串中的用户名和密码
# mongodb://root:123456@<地址>:27017
# 如果需要重置密码,删除数据卷重新部署
docker stack rm review_mongodb
sleep 10
docker volume rm review_mongodb_data_db
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb
18.9.4 磁盘空间不足
问题: MongoDB 写入失败,提示磁盘空间不足
原因: 数据卷所在磁盘空间不足
解决:
# 检查磁盘使用
df -h
# 检查 MongoDB 数据大小
docker exec $(docker ps -q -f name=review_mongodb_db) du -sh /bitnami/mongodb
# 清理不需要的数据或扩容磁盘
二十、部署 Redis Sentinel 集群(灾备从节点模式)
20.1 服务说明
本环境的 Redis 作为生产环境的灾备从节点部署,平时仅同步数据不提供服务,生产故障时可手动切换为独立集群。
| 项目 | 值 |
|---|---|
| Stack 名称 | review_redis |
| 镜像 | bitnami/redis:7.0.11, bitnami/redis-sentinel:7.0.11 |
| Redis 端口 | 6379 |
| Sentinel 端口 | 26379(故障切换后启用) |
| 模式 | 隐藏从节点 + 级联复制(灾备模式) |
| 生产主节点 | 192.168.10.56:6379 |
| 密码 | sino#650 |
| Sentinel 密码 | sino#650 |
| 状态 | ✅ 已部署 |
20.2 架构说明
20.2.1 平时状态(数据同步,不提供服务)
┌─────────────────────────────────────────────────────────────────────────────┐
│ 生产环境 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Sentinel-1 │ │ Sentinel-2 │ │
│ │ 10.55:26379 │ │ 10.56:26379 │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ 监控 mymaster │ │
│ └────────────┬──────────┘ │
│ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 10.55 │←───────│ 10.56 │ │
│ │ (Slave) │ 复制 │ (Master) │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
└───────────────────────────────────┼────────────────────────────────────────┘
│
│ 复制 (replica-announced no,生产看不到)
↓
┌───────────────────────────────────────────────────────────────────────────────┐
│ Review 环境(灾备) │
│ │
│ ┌──────────────────┐ │
│ │ 132 (master) │ ← 从 10.56 同步,对生产 Sentinel 隐藏 │
│ │ ZD-BAK-APP2 │ │
│ │ 隐藏 Slave │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ 级联复制 │ │
│ ↓ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 133 (slave-1)│ │ 134 (slave-2)│ ← 从 132 同步,可被 Review Sentinel 发现 │
│ │ zd-bak-app3 │ │ ZD-BAK-APP1 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ⚠️ 平时无 Sentinel,仅同步数据 │
└───────────────────────────────────────────────────────────────────────────────┘
20.2.2 故障切换后(独立 Sentinel 集群)
┌───────────────────────────────────────────────────────────────────────────────┐
│ Review 环境(接管服务) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Sentinel-1 │ │ Sentinel-2 │ │ Sentinel-3 │ │
│ │ 132:26379 │ │ 133:26379 │ │ 134:26379 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ 监控 reviewmaster │
│ ↓ │
│ ┌──────────────────┐ │
│ │ 132 (Master) │ ← SLAVEOF NO ONE 提升为主节点 │
│ │ ZD-BAK-APP2 │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ 复制 │ │
│ ↓ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 133 (Slave) │ │ 134 (Slave) │ │
│ │ zd-bak-app3 │ │ ZD-BAK-APP1 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ✅ 完整 Sentinel 集群,支持自动故障转移 │
└───────────────────────────────────────────────────────────────────────────────┘
20.2.3 关键配置说明
| 节点 | replica-announced no | replica-priority 0 | 说明 |
|---|---|---|---|
| 132 | ✅ 是 | ✅ 是 | 对生产 Sentinel 隐藏,不参与生产选举 |
| 133 | ❌ 否 | ❌ 否 | 可被 Review Sentinel 发现 |
| 134 | ❌ 否 | ❌ 否 | 可被 Review Sentinel 发现 |
为什么这样配置?
- 132 需要隐藏:132 直接从生产 10.56 复制,如果不隐藏,生产 Sentinel 会发现它并可能在故障时将其选为主节点
- 133/134 不需要隐藏:它们从 132 复制,生产 Sentinel 本来就看不到;故障切换后需要被 Review Sentinel 发现
20.3 部署前准备
20.3.1 验证生产主节点可访问
# 1. 测试网络连通性
ping 192.168.10.56
# 2. 测试 Redis 端口是否开放
nc -zv 192.168.10.56 6379
# 3. 测试 Redis 连接和认证(如果服务器有 redis-cli)
redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' PING
# 应返回 PONG
# 4. 检查 10.56 Redis 是否为主节点
redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' INFO replication
# 应显示 role:master
# 如果没有 redis-cli,用 Docker 临时执行
docker run --rm bitnami/redis:7.0.11 redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' PING
docker run --rm bitnami/redis:7.0.11 redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' INFO replication
20.3.2 拉取镜像(所有节点)
由于服务分布在 3 个节点上,需要在每个节点都拉取镜像。
方法一:直接拉取(每个节点执行)
docker pull bitnami/redis:7.0.11
docker pull bitnami/redis-sentinel:7.0.11
方法二:通过私有仓库中转(推荐)
# 1. 在能访问外网的服务器上操作
docker pull bitnami/redis:7.0.11
docker pull bitnami/redis-sentinel:7.0.11
docker tag bitnami/redis:7.0.11 harbor.sino-assist.com/bitnami/redis:7.0.11
docker tag bitnami/redis-sentinel:7.0.11 harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11
docker push harbor.sino-assist.com/bitnami/redis:7.0.11
docker push harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11
# 2. 在每个目标节点上拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/redis:7.0.11
docker pull harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11
docker tag harbor.sino-assist.com/bitnami/redis:7.0.11 bitnami/redis:7.0.11
docker tag harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11 bitnami/redis-sentinel:7.0.11
20.4 配置文件说明
env_review 配置项
| 配置项 | 值 | 说明 |
|---|---|---|
| NAMESPACE | review | 命名空间 |
| REDIS_PASSWORD | sino#650 | Redis 密码(与生产相同) |
| REDIS_SENTINEL_PASSWORD | sino#650 | Sentinel 密码 |
| REDIS_PROD_MASTER_HOST | 192.168.10.56 | 生产主节点地址 |
| REDIS_PROD_MASTER_PORT | 6379 | 生产主节点端口 |
| REDIS_REVIEW_PRIMARY_HOST | 192.168.3.132 | Review 主同步节点(132) |
docker-compose.yml 关键配置
132 节点(主同步节点)配置:
| 配置项 | 值 | 说明 |
|---|---|---|
| REDIS_REPLICATION_MODE | slave | 从节点模式 |
| REDIS_MASTER_HOST | ${REDIS_PROD_MASTER_HOST} | 生产主节点地址 (10.56) |
| REDIS_EXTRA_FLAGS | --replica-announced no --replica-priority 0 | 隐藏 + 不参与选举 |
| ports mode | host | 使用主机网络端口 |
133/134 节点(级联从节点)配置:
| 配置项 | 值 | 说明 |
|---|---|---|
| REDIS_REPLICATION_MODE | slave | 从节点模式 |
| REDIS_MASTER_HOST | ${REDIS_REVIEW_PRIMARY_HOST} | Review 主节点 (132) |
| ports mode | host | 使用主机网络端口 |
REDIS_EXTRA_FLAGS 详解:
| 参数 | 作用 |
|---|---|
--replica-announced no |
不向 Sentinel 通告自己,对 Sentinel 隐藏 |
--replica-priority 0 |
设置优先级为 0,永不被选举为主节点 |
20.5 部署命令
cd /opt/swarm/support/redis-review-132
# 转换 Windows 换行符(如有需要)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_redis
20.6 验证部署
20.6.1 查看服务状态
# 查看服务列表
docker service ls | grep review_redis
# 预期输出(3 个服务):
# review_redis_master replicated 1/1
# review_redis_slave-1 replicated 1/1
# review_redis_slave-2 replicated 1/1
# 查看详细状态
docker stack ps review_redis --no-trunc | grep -v Shutdown
20.6.2 验证同步状态
注意: 以下命令统一使用 Docker 方式执行,无需在服务器上安装 redis-cli。优先使用已运行的
review_redis_master容器,如果容器未运行可使用临时容器方式。
方式一:使用已运行的容器(推荐)
# 检查 132 (从 10.56 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.10.56
# master_link_status:up
# 检查 133 (从 132 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.3.132
# master_link_status:up
# 检查 134 (从 132 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.3.132
# master_link_status:up
方式二:使用临时容器(备选,容器未运行时使用)
# 定义 Redis 镜像
REDIS_IMAGE="bitnami/redis:7.0.11"
# 检查 132
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication
# 检查 133
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication
# 检查 134
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication
20.6.3 验证生产 Sentinel 看不到 Review 节点
# 在生产 Sentinel 上查看从节点列表(使用已运行容器)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.10.55 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL replicas mymaster
# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.10.55 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL replicas mymaster
# 预期:不应该看到 192.168.3.132、192.168.3.133、192.168.3.134
20.6.4 测试数据同步
# 在从节点查询数据(只读)- 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning KEYS '*' | head -10
# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning KEYS '*' | head -10
# 尝试写入(应该失败,从节点只读)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SET test_key test_value
# 预期输出:(error) READONLY You can't write against a read only replica.
20.7 连接信息
平时状态(灾备同步):
| 节点 | Redis 地址 | 角色 |
|---|---|---|
| ZD-BAK-APP2 | 192.168.3.132:6379 | 隐藏从节点(从 10.56 同步) |
| zd-bak-app3 | 192.168.3.133:6379 | 从节点(从 132 同步) |
| ZD-BAK-APP1 | 192.168.3.134:6379 | 从节点(从 132 同步) |
| 项目 | 值 |
|---|---|
| Redis 密码 | sino#650 |
| 生产主节点 | 192.168.10.56:6379 |
故障切换后:
| 项目 | 值 |
|---|---|
| Review Master | 192.168.3.132:6379 |
| Review Sentinel | 192.168.3.132:26379, 192.168.3.133:26379, 192.168.3.134:26379 |
| Sentinel Master 名称 | reviewmaster |
20.8 故障切换
20.8.1 切换脚本
项目目录下提供了 failover-to-review.sh 脚本用于故障切换:
# 添加执行权限
chmod +x failover-to-review.sh
# 查看当前状态
./failover-to-review.sh status
# 完整故障切换(提升 132 为 Master + 部署 Sentinel)
./failover-to-review.sh full
# 仅执行故障切换(不部署 Sentinel)
./failover-to-review.sh failover
# 仅部署 Sentinel
./failover-to-review.sh sentinel
# 回滚到生产
./failover-to-review.sh rollback
20.8.2 手动切换步骤
如果不使用脚本,可按以下步骤手动操作:
注意: 以下命令优先使用已运行的
review_redis_master容器执行,如果容器未运行可使用临时容器方式(备选命令)。
步骤 1:提升 132 为 Master
# 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning SLAVEOF NO ONE
# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SLAVEOF NO ONE
步骤 2:确认 133/134 指向 132
# 检查 133(使用已运行容器)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
# 如果不是 132,重新配置:
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
# 检查 134(使用已运行容器)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
# 如果不是 132,重新配置:
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
步骤 3:部署 Review Sentinel
cd /opt/swarm/support/redis-review-132
env $(cat ./env_review | xargs) envsubst < ./docker-compose-sentinel.yml | docker stack deploy --compose-file - review_sentinel
步骤 4:验证 Sentinel
# 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL master reviewmaster
# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL master reviewmaster
# 应显示 ip=192.168.3.132, port=6379, flags=master
步骤 5:修改应用配置
# Spring Boot 配置
spring:
redis:
sentinel:
master: reviewmaster
nodes: 192.168.3.132:26379,192.168.3.133:26379,192.168.3.134:26379
password: sino#650
password: sino#650
20.8.3 回滚到生产
推荐使用安全回滚脚本:
./failover-to-review.sh rollback,该脚本会先将故障期间的数据同步到生产,确保数据不丢失。
使用已运行容器(推荐):
# 1. 移除 Review Sentinel
docker stack rm review_sentinel
# 2. 重新配置 132 指向生产
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning SLAVEOF 192.168.10.56 6379
# 3. 确认 133/134 指向 132
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
# 4. 验证同步状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning INFO replication
备选:使用临时容器
REDIS_IMAGE="bitnami/redis:7.0.11"
# 1. 移除 Review Sentinel
docker stack rm review_sentinel
# 2. 重新配置 132 指向生产
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.10.56 6379
# 3. 确认 133/134 指向 132
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
# 4. 验证同步状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication
20.9 常用运维命令
使用已运行容器(推荐):
# 查看所有服务状态
docker stack ps review_redis
# 查看某个服务日志
docker service logs review_redis_master --tail 100
docker service logs review_redis_slave-1 --tail 100
# 在指定节点查看容器
docker ps | grep review_redis
# 进入 Redis CLI(交互模式)
docker exec -it $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning
# 查看复制状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning INFO replication
# 查看远程节点复制状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication
# 强制重启某个服务
docker service update --force review_redis_master
# 删除整个 Stack
docker stack rm review_redis
备选:使用临时容器(容器未运行时使用)
REDIS_IMAGE="bitnami/redis:7.0.11"
# 进入 Redis CLI(交互模式)
docker run --rm -it --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning
# 查看复制状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication
# 查看远程节点复制状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication
20.10 本次部署遇到的问题及解决方案总结
20.10.1 问题一:Review Redis 节点被生产 Sentinel 发现
现象:
- 将 Review Redis (132, 133, 134) 部署为生产 10.56 的从节点
- 生产 Sentinel 发现了 Review 节点,并在故障时将 Review 节点选为主节点
- 导致生产 Master 切换到 Review 环境
原因:
- Redis 从节点默认会向 Sentinel 通告自己的存在
- Sentinel 通过
INFO replication命令自动发现所有从节点 - 生产 Sentinel 将 Review 节点纳入了选举范围
解决方案:
# 使用 REDIS_EXTRA_FLAGS 配置隐藏
environment:
- REDIS_EXTRA_FLAGS=--replica-announced no --replica-priority 0
| 参数 | 作用 |
|---|---|
--replica-announced no |
不向 Sentinel 通告自己,对 Sentinel 隐藏 |
--replica-priority 0 |
设置优先级为 0,即使被发现也不会被选举为主节点 |
注意: REDIS_REPLICA_PRIORITY=0 环境变量在 Bitnami 镜像中不生效,必须使用 REDIS_EXTRA_FLAGS。
20.10.2 问题二:Review Sentinel 无法正常监控
现象:
- 部署 Review Sentinel 监控 132 作为 master
- Sentinel 不断触发故障转移,将 master 从 132 切换到 133 或 134
- 切换后又切回,形成循环
原因:
- Sentinel 通过
INFO replication检测节点角色 - 132 因为从 10.56 复制,role 永远是
slave - Sentinel 发现监控的 "master" 是 slave,认为需要故障转移
解决方案:
采用"灾备模式":
- 平时不部署 Review Sentinel,仅同步数据
- 生产故障时,先将 132 提升为真正的 Master(
SLAVEOF NO ONE) - 然后再部署 Review Sentinel
20.10.3 问题三:env_review 文件中的注释导致部署失败
现象:
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_redis
报错或环境变量为空
原因:
xargs无法正确处理#开头的注释行- 导致环境变量解析错误
解决方案:
- env_review 文件中不要使用注释
- 或使用过滤:
env $(cat ./env_review | grep -v '^#' | xargs)
20.10.4 问题四:Sentinel 数据卷缓存旧配置
现象:
- 修改 Sentinel 配置后重新部署
- Sentinel 仍然使用旧的 master 配置
原因:
- Sentinel 会将运行时配置写入数据卷
- 重新部署时读取了旧的配置文件
解决方案:
# 删除 Sentinel 数据卷后重新部署
docker volume rm review_redis_sentinel_data_1
docker volume rm review_redis_sentinel_data_2
docker volume rm review_redis_sentinel_data_3
20.10.5 问题五:133/134 配置 replica-announced no 导致故障切换后 Sentinel 发现不了
现象:
- 所有节点都配置了
replica-announced no - 故障切换后,Review Sentinel 无法发现 133/134 作为从节点
原因:
replica-announced no对所有 Sentinel 生效,不区分生产和 Review- 133/134 从 132 复制,生产 Sentinel 本来就看不到它们
解决方案:
- 只有 132 需要配置
replica-announced no(因为它直接从生产复制) - 133/134 不需要配置,因为生产 Sentinel 通过 132 也发现不了它们
20.11 架构设计总结
| 设计目标 | 实现方式 |
|---|---|
| Review 同步生产数据 | 132 从 10.56 复制,133/134 从 132 级联复制 |
| 对生产 Sentinel 隐藏 | 132 配置 replica-announced no |
| 不影响生产选举 | 132 配置 replica-priority 0 |
| 故障时可独立运行 | 提供切换脚本和独立 Sentinel 配置 |
| 切换后支持高可用 | 部署 Review Sentinel 监控 reviewmaster |
| 可回滚到生产 | 提供 rollback 命令重新指向 10.56 |
20.12 文件清单
| 文件 | 说明 |
|---|---|
docker-compose.yml |
Review Redis 配置(平时使用) |
docker-compose-sentinel.yml |
Review Sentinel 配置(故障切换后部署) |
env_review |
环境变量 |
failover-to-review.sh |
故障切换脚本 |
二十一、服务部署命令汇总
20.1 通用部署模式
cd /path/to/service
# 使用环境变量文件部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - <stack名称>
19.2 常用管理命令
# 查看所有 Stack
docker stack ls
# 查看 Stack 中的服务
docker stack ps <stack名称>
# 查看服务日志
docker service logs <服务名>
# 删除 Stack
docker stack rm <stack名称>
# 更新服务
docker service update --force <服务名>
# 查看所有服务
docker service ls
# 查看镜像
docker images | grep <关键字>
# 查看 Docker Config
docker config ls
二十二、待部署服务清单
无依赖服务
| 服务 | 端口 | 状态 | 说明 |
|---|---|---|---|
| Redis Sentinel | 6379, 26379 | ✅ 已部署 | 缓存服务(从 10.56 同步) |
| MongoDB | 27017 | ✅ 已部署 | NoSQL 数据库 |
| RabbitMQ | 5672, 15672 | ✅ 已部署 | 消息队列 |
| Elasticsearch | 9200, 9300 | ✅ 已部署 | 搜索引擎 |
| MinIO | 9000 | ⏳ 待部署 | 对象存储 |
| Jenkins | 8081 | ⏳ 待部署 | CI/CD |
| Monitor | 3000 | ⏳ 待部署 | Grafana + Prometheus |
| ClickHouse | 8123 | ⏳ 待部署 | OLAP 分析数据库 |
有依赖服务
| 服务 | 依赖 | 状态 | 说明 |
|---|---|---|---|
| MySQL 主从 (mysql-repl-tool) | 无 | ✅ 已部署 | 工具类服务专用数据库 |
| Nacos | MySQL | ✅ 已部署 | 服务注册/配置中心 |
| XXL-Job-Admin | MySQL | ✅ 已部署 | 分布式任务调度 |
| Kibana | Elasticsearch | ✅ 已部署 | ES 可视化 |
| SkyWalking | Elasticsearch | ✅ 已部署 | APM 链路追踪 |
| Log (Logstash + Filebeat) | Elasticsearch | ✅ 已部署 | 日志处理 |
| Canal | MySQL, RabbitMQ | ✅ 已部署 | 数据同步 |
二十三、服务依赖关系图
┌─────────┐
│ Network │ ← 必须最先创建
└────┬────┘
│
┌────┴────┐
│Portainer│ ← 管理入口
└────┬────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ MySQL │ │ Redis │ │ ES │
└────┬────┘ └─────────┘ └────┬────┘
│ │
┌────┴────────────┐ ┌────┴────┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────┐ ┌──────────┐ ┌───────┐ ┌──────────┐
│ Nacos │ │XXL-Job │ │Kibana │ │SkyWalking│
└───┬───┘ │Admin │ └───────┘ └──────────┘
│ └──────────┘
▼
┌─────────────────┐
│ 业务微服务 │
│ (注册到 Nacos) │
└─────────────────┘
附录:目录结构
docker-swarm-review/
├── 10.5x环境配置记录/ # 环境部署文档
├── portainer/ # Portainer 管理平台
├── monitor/ # Grafana + Prometheus
├── mysql/ # MySQL 单机
├── mysql-repl-tool/ # MySQL 主从复制
├── redis/ # Redis 主从 + Sentinel
├── redis-review-132/ # Review 环境 Redis
├── mongodb/ # MongoDB
├── rabbitmq/ # RabbitMQ
├── elasticsearch/ # Elasticsearch
├── nacos/ # Nacos 单机
├── nacos-cluser/ # Nacos 集群
├── xxl-job-admin/ # XXL-Job
├── jenkins/ # Jenkins
├── canal/ # Canal
├── clickhouse/ # ClickHouse
├── minio/ # MinIO
├── nginx/ # Nginx
├── nginx-review-132/ # Review 环境 Nginx
├── skywalking/ # SkyWalking
├── log/ # ELK 日志
└── datart/ # BI 数据可视化
二十四、基础镜像详解
22.1 什么是基础镜像
基础镜像是构建应用镜像的"地基",包含运行应用所需的操作系统、运行时环境和通用工具。
基础镜像地址: harbor.sino-assist.com/marsal1212/java11:latest
22.2 基础镜像结构
harbor.sino-assist.com/marsal1212/java11:latest
│
├── FROM openjdk:11-jdk # 官方 OpenJDK 11 镜像(Debian 系统)
│
├── 系统工具
│ ├── curl # HTTP 请求工具(健康检查用)
│ ├── wget # 下载工具
│ ├── procps # 进程管理工具(ps、top)
│ ├── lsof # 查看打开文件
│ ├── iotop # IO 监控
│ └── lib32gcc-s1 # 32位兼容库
│
└── /skywalking-agent/ # SkyWalking Agent(链路追踪)
├── skywalking-agent.jar # Agent 主程序
├── config/
│ └── agent.config # 配置文件
├── plugins/ # 默认插件(自动加载)
├── optional-plugins/ # 可选插件
├── activations/ # 工具包激活器
└── bootstrap-plugins/ # 引导插件
22.3 Dockerfile 解析
FROM --platform=linux/amd64 openjdk:11-jdk
# 基于官方 OpenJDK 11 镜像,强制使用 amd64 架构(兼容性)
RUN apt-get update && apt-get install -y curl iotop wget procps lsof lib32gcc-s1
# 安装运维/调试常用工具:
# - curl: 用于容器内健康检查(curl localhost:8080/actuator/health)
# - procps: 提供 ps、top 命令,排查问题时查看进程
# - lsof: 查看端口占用、文件句柄
# - iotop: IO 性能排查
ADD skywalking-agent /skywalking-agent
# 将本地 skywalking-agent 目录复制到镜像的 /skywalking-agent 路径
# 这样所有基于此镜像的应用都可以直接使用 Agent
CMD ["java","--version"]
# 默认命令(会被应用镜像覆盖)
22.4 镜像层级关系
┌─────────────────────────────────────────────────────────┐
│ 应用镜像 (harbor.sino-assist.com/sa-server/sa-gateway) │ ← Jib 构建
│ - 应用 JAR 包 │
│ - 应用配置 │
├─────────────────────────────────────────────────────────┤
│ 基础镜像 (harbor.sino-assist.com/marsal1212/java11) │ ← 自定义镜像
│ - SkyWalking Agent │
│ - curl/wget/procps 等工具 │
├─────────────────────────────────────────────────────────┤
│ openjdk:11-jdk │ ← 官方镜像
│ - JDK 11 运行时 │
│ - Debian 操作系统 │
└─────────────────────────────────────────────────────────┘
22.5 基础镜像如何被使用
Jib 构建时引用
在 sa-server 项目的 build.gradle 中配置:
jib {
from {
image = "harbor.sino-assist.com/marsal1212/java11:latest" // 基础镜像
}
to {
image = "${docker_hub}/sa-server/${project.name}" // 目标镜像
tags = ["${docker_version}"]
}
}
构建流程
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 源代码编译 │ ──► │ Jib 打包 │ ──► │ 推送到 Harbor │
│ (Gradle) │ │ (基于基础镜像) │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Jib 做的事情:
1. 拉取基础镜像 (java11:latest)
2. 在其之上添加应用层(JAR、配置)
3. 设置启动命令
4. 推送到 Harbor
最终应用镜像内容
harbor.sino-assist.com/sa-server/sa-gateway:master
│
├── /skywalking-agent/ # 来自基础镜像
│ └── skywalking-agent.jar
│
├── /app/ # Jib 添加的应用层
│ ├── classes/ # 编译后的类文件
│ ├── libs/ # 依赖 JAR
│ └── resources/ # 配置文件
│
└── ENTRYPOINT: java -cp /app/... MainClass
22.6 运行时如何启用 SkyWalking
配置位置说明
重要: SkyWalking 相关配置已在 build.gradle 的 Jib entrypoint 中设置,无需在 pipeline-script 中重复配置。
// build.gradle 中的 Jib entrypoint 配置
entrypoint = ["/bin/sh", "-c",
'java -javaagent:/skywalking-agent/skywalking-agent.jar ' +
'-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=${namespace}-skywalking-oap:11800 ' +
'-DSW_AGENT_NAME=$project_name ' +
'-DSW_AGENT_INSTANCE_NAME=$project_name:${nativeIp} ' +
...
]
容器启动流程
1. Docker 启动容器
│
2. 执行 Jib 配置的 entrypoint(包含 -javaagent 参数)
│ 命令: java -javaagent:/skywalking-agent/skywalking-agent.jar ...
│
3. SkyWalking Agent 加载
│
4. Agent 读取系统属性(-D 参数)
│ - -DSW_AGENT_NAME=$project_name → 服务名
│ - -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=${namespace}-skywalking-oap:11800 → OAP 地址
│
5. Agent 开始采集链路数据,上报到 OAP
关键环境变量(由 pipeline-script 设置,被 entrypoint 引用)
| 环境变量 | 作用 | 示例值 |
|---|---|---|
namespace |
用于构建 OAP 地址 ${namespace}-skywalking-oap:11800 |
review |
project_name |
服务名(SkyWalking UI 显示) | sa-gateway |
nativeIp |
实例名后缀 | 192.168.3.132 |
22.7 基础镜像的优势
| 优势 | 说明 |
|---|---|
| 统一管理 | 所有服务使用相同的 JDK 版本和工具 |
| 减少重复 | SkyWalking Agent 只需打包一次 |
| 快速构建 | Jib 只需添加应用层,不用重复下载基础层 |
| 便于升级 | 更新基础镜像后,重新构建应用即可生效 |
| 运维友好 | 内置 curl/lsof 等工具,方便排查问题 |
22.8 如何更新基础镜像
如果需要更新 SkyWalking Agent 版本或添加工具:
# 1. 修改 Dockerfile 或更新 skywalking-agent 目录
# 2. 重新构建基础镜像
cd builder-docker/java11
docker build -f Dockerfile -t harbor.sino-assist.com/marsal1212/java11:latest .
# 3. 推送到 Harbor
docker push harbor.sino-assist.com/marsal1212/java11:latest
# 4. 重新构建所有应用镜像(Jenkins 中重新打包)
二十五、Jenkins Pipeline 部署脚本
23.1 脚本概述
Jenkins Pipeline 脚本用于自动化构建和部署微服务应用到 Docker Swarm 集群。
脚本位置: docker-swarm-review/pipeline-script
备份文件: docker-swarm-review/jenkins/pipeline-script-backup-with-comments
23.2 脚本功能
| 阶段 | 功能 |
|---|---|
| checkout | 从 GitLab 拉取代码 |
| docker-build-push | 使用 Gradle Jib 构建镜像并推送到 Harbor |
| docker-deploy | 通过 SSH 远程部署到 Docker Swarm |
23.3 Review 环境配置
| 配置项 | 值 | 说明 |
|---|---|---|
| harbor | harbor.sino-assist.com | 镜像仓库地址 |
| deploy_server | 192.168.3.132 | 部署服务器(Manager 节点) |
| profile | review | Spring 环境 |
| nacos_address | review-nacos1:8848,review-nacos2:8848,review-nacos3:8848 | Nacos 集群地址 |
| namespace | review | 命名空间(同时用于构建 SkyWalking OAP 地址) |
| SSH 用户 | root | SSH 登录用户 |
| 网络子网 | 10.18.0.0/16 | Docker Overlay 网络 |
23.4 节点映射表
| IP | 主机名 | 角色 |
|---|---|---|
| 192.168.3.132 | ZD-BAK-APP2 | Manager (Leader) |
| 192.168.3.133 | zd-bak-app3 | Worker |
| 192.168.3.134 | ZD-BAK-APP1 | Worker |
23.5 完整脚本(含详细注释)
#!/usr/bin/env groovy
// ============================================================================
// Jenkins Pipeline 脚本(Review 环境)
// 更新时间:2025-12-23
// ============================================================================
import groovy.json.JsonSlurperClassic
// 导入 Groovy JSON 解析类,用于解析 Jenkins 参数传入的 JSON 配置
//properties(projectProperties)
def jsonOption = new JsonSlurperClassic().parseText(params.modulesOption)
// 解析 Jenkins 参数 modulesOption(JSON 格式),转换为 Groovy 对象
// params.modulesOption 是 Jenkins 参数化构建时传入的 JSON 字符串
echo "jsonOption ${jsonOption}"
// 打印解析后的配置信息,用于调试
// ============================================================================
// 环境固定配置(Review 环境)
// ============================================================================
jsonOption.harbor = "harbor.sino-assist.com"
// Harbor 私有镜像仓库地址
jsonOption.deploy_server = "192.168.3.132"
// 部署目标服务器 IP(Swarm Manager 节点 ZD-BAK-APP2)
jsonOption.profile = "review"
// Spring Boot 激活的配置环境(application-review.yml)
jsonOption.nacos_address = "review-nacos1:8848,review-nacos2:8848,review-nacos3:8848"
// Nacos 集群地址(使用 Docker Overlay 网络内的服务名 + 内部端口)
// 三个 Nacos 节点分别部署在:
// - review-nacos1 -> ZD-BAK-APP1 (192.168.3.134)
// - review-nacos2 -> ZD-BAK-APP2 (192.168.3.132)
// - review-nacos3 -> zd-bak-app3 (192.168.3.133)
jsonOption.namespace = "review"
// Nacos 命名空间,同时也是 Docker Overlay 网络名称
// 注意:namespace 也用于 build.gradle 中构建 SkyWalking OAP 地址:${namespace}-skywalking-oap:11800
// ============================================================================
// 变量定义
// ============================================================================
def branch = params.branch
// Git 分支名(从 Jenkins 参数传入)
def DOCKER_CREDENTIAL_ID = 'harbor'
// Jenkins 凭据 ID,用于登录 Harbor 镜像仓库
def REGISTRY_URL = jsonOption.harbor
// 镜像仓库地址
def IMAGE_TAG = params.branch
// 镜像标签(使用 Git 分支名作为标签)
def deploy_modules = jsonOption.deploy_modules
// 要部署的模块列表(从 JSON 参数中获取)
def deploy_server = jsonOption.deploy_server
// 部署服务器 IP
def deploy_step = jsonOption.deploy_step
// 部署步骤(可能包含"打包镜像"和/或"部署服务")
def deploy_project_names = ""
// Gradle 构建的项目名列表(拼接后的字符串)
// ============================================================================
// 构建模块名拼接
// ============================================================================
for (module in deploy_modules) {
if (module.o == true) {
// module.o 表示该模块是否被选中部署
deploy_project_names += " ${module.module}:jib "
// 拼接 Gradle jib 任务名
// 结果类似: " sa-gateway:jib sa-auth:jib sa-system:jib "
// jib 是 Google 的容器镜像构建工具,无需 Docker daemon
}
}
// ============================================================================
// 主流程 node 块
// ============================================================================
node {
// node 块定义 Jenkins 执行节点,在此节点上执行所有 stage
def gradleHome = tool 'gradle'
// 获取 Jenkins 全局工具配置中名为 'gradle' 的 Gradle 安装路径
def gradle = "${gradleHome}/bin/gradle"
// Gradle 可执行文件的完整路径
// ========================================================================
// Stage 1: 拉取代码
// ========================================================================
stage('checkout') {
git branch: branch, credentialsId: 'gitlab', url: 'https://git.sino-assist.com/server/sa-server.git'
// 从 GitLab 克隆代码
// - branch: 要拉取的分支(从参数传入)
// - credentialsId: Jenkins 中配置的 GitLab 凭据 ID
// - url: Git 仓库地址
}
// ========================================================================
// Stage 2: 构建镜像并推送到 Harbor
// ========================================================================
stage('docker-build-push') {
if (deploy_step.contains("打包镜像")) {
// 判断部署步骤是否包含"打包镜像"
withCredentials([usernamePassword(passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME', credentialsId: "${DOCKER_CREDENTIAL_ID}",)]) {
// 从 Jenkins 凭据库获取 Harbor 的用户名和密码
// 用户名存入 DOCKER_USERNAME 变量
// 密码存入 DOCKER_PASSWORD 变量
sh "docker login $REGISTRY_URL -u '$DOCKER_USERNAME' -p '$DOCKER_PASSWORD'"
// 登录 Harbor 镜像仓库
}
sh "$gradle $deploy_project_names -x test --parallel --build-cache -Pdocker_hub='$REGISTRY_URL' -Pdocker_version=$IMAGE_TAG -Djib.console=plain"
// 执行 Gradle jib 任务,构建并推送 Docker 镜像
// 参数说明:
// $deploy_project_names: 要构建的模块列表(如 sa-gateway:jib)
// -x test: 跳过单元测试
// --parallel: 并行构建多个模块
// --build-cache: 启用构建缓存,加速构建
// -Pdocker_hub: 传递镜像仓库地址给 build.gradle
// -Pdocker_version: 传递镜像版本标签(分支名)
// -Djib.console=plain: jib 输出格式为纯文本(适合 CI 日志)
}
}
// ========================================================================
// Stage 3: 部署服务到 Docker Swarm
// ========================================================================
if (deploy_step.contains("部署服务")) {
// 判断部署步骤是否包含"部署服务"
stage('docker-deploy') {
for (final module in deploy_modules) {
if (module.o == true) {
// 遍历所有被选中的模块
def modules = module.module.split(":")
module.projectName = modules[modules.length - 1]
// 解析模块名
// 如 "sa-modules:sa-gateway" -> 取最后一段 "sa-gateway"
module.imageTag = IMAGE_TAG
// 设置镜像标签
echo "deploy module ${module.module}"
// 打印正在部署的模块名
def services = docker_service_param(module, jsonOption)
// 调用 docker_service_param 函数生成服务部署参数
echo "部署服务"
for (final def svc in services) {
// 遍历每个服务实例(一个模块可能部署多个实例)
String yml = makeYML(svc)
// 调用 makeYML 函数生成 Docker Stack YAML 配置
String serverName = svc.get("serviceName")
// 获取服务名(如 ss132_sa-gateway)
String ymlFile = "/data/swarm/${serverName}.yml"
// YAML 文件存放路径
String deploy = "sshpass -p 'Sino.2025' ssh root@${deploy_server} \" mkdir -p /data/swarm/ && echo '''${yml}''' > ${ymlFile} && docker stack deploy -c ${ymlFile} ${serverName} --prune --with-registry-auth\""
// 通过 SSH 远程执行部署命令:
// sshpass -p 'Sino.2025': 使用密码登录(避免交互式输入)
// ssh root@${deploy_server}: SSH 到部署服务器
// mkdir -p /data/swarm/: 创建存放 yml 文件的目录
// echo '''${yml}''' > ${ymlFile}: 将生成的 YAML 写入文件
// docker stack deploy: 部署 Docker Stack
// -c ${ymlFile}: 指定 compose 文件
// ${serverName}: Stack 名称
// --prune: 删除不再存在于 compose 文件中的服务
// --with-registry-auth: 将 registry 认证信息传递给 swarm agents
echo deploy
// 打印部署命令(用于调试)
sh deploy
// 执行部署命令
}
}
}
}
}
}
// ============================================================================
// makeYML 函数:生成 Docker Stack YAML 配置
// ============================================================================
def makeYML(params) {
return """
version: \\"3.8\\"
services:
svc:
image: ${params.IMAGE}
# 镜像地址,如 harbor.sino-assist.com/sa-server/sa-gateway:master
environment:
- active_profile=${params.profile}
# Spring Boot 激活的配置环境
- nacos_address=${params.nacos_address}
# Nacos 服务地址
- nacos_password=gkxl2024#@
# Nacos 登录密码
- namespace=${params.namespace}
# Nacos 命名空间
- project_name=${params.projectName}
# 项目名称(用于日志、监控等标识)
- params=${params.params}
# 额外的 JVM 参数(如网络配置)
- nativeIp=${params.nativeIp}
# 服务实例绑定的 IP 地址
- reservationsMemory=${params.reservationsMemory}
# 预留内存(传递给应用)
- limitMemory=${params.limitMemory}
# 内存限制(传递给应用)
- TZ=Asia/Shanghai
# 时区设置
# 注意:SkyWalking 相关配置(SW_AGENT_NAME、SW_AGENT_COLLECTOR_BACKEND_SERVICES、JAVA_TOOL_OPTIONS)
# 已在 build.gradle 的 Jib entrypoint 中配置,无需在此重复设置
# OAP 地址通过 namespace 变量构建:${namespace}-skywalking-oap:11800
ports:
- '${params.port}:8080'
# 端口映射:宿主机端口:容器端口
# 容器内应用统一监听 8080 端口
healthcheck:
test: \\"curl --fail --silent localhost:8080/actuator/health/ping | grep UP || exit 1\\"
# 健康检查命令:
# curl 访问 Spring Boot Actuator 健康检查端点
# grep UP 检查返回值是否包含 UP
# 失败则返回 exit 1
interval: 15s
# 检查间隔:每 15 秒检查一次
timeout: 5s
# 超时时间:5 秒内无响应视为失败
retries: 20
# 重试次数:连续 20 次失败后标记为 unhealthy
volumes:
- ${params.namespace}_logs:/logs
# 日志卷挂载:将容器内 /logs 目录映射到命名卷
logging:
driver: json-file
# 日志驱动:使用 Docker 默认的 json-file 驱动
options:
max-size: "1G"
# 单个日志文件最大 1GB
max-file: "3"
# 最多保留 3 个日志文件(滚动)
extra_hosts:
- "hostname:127.0.0.1"
# 添加 hosts 记录
- "open.property.cic.cn:59.46.218.8"
# 外部服务 hosts 记录
deploy:
mode: replicated
# 部署模式:replicated(指定副本数)
replicas: 1
# 副本数量:1 个实例
restart_policy:
condition: on-failure
# 重启策略:仅在失败时重启
delay: 5s
# 重启延迟:5 秒后重启
max_attempts: 3
# 最大重启次数:3 次
update_config:
order: stop-first
# 更新策略:先停止旧容器,再启动新容器
# 适合资源紧张的环境,但会有短暂服务中断
resources:
limits:
cpus: \\"${params.limitCpu}\\"
# CPU 限制(如 "2" 表示最多使用 2 核)
memory: ${params.limitMemory}
# 内存限制(如 "2G")
reservations:
cpus: \\"${params.reservationsCpu}\\"
# CPU 预留(保证最少可用的 CPU 资源)
memory: ${params.reservationsMemory}
# 内存预留(保证最少可用的内存资源)
placement:
constraints:
- "node.hostname==${params.hostname}"
# 节点约束:只在指定主机名的节点上运行
# 用于将服务固定到特定服务器
networks:
default:
name: ${params.namespace}
# 网络名称:review
external: true
# 使用外部已存在的网络(不自动创建)
volumes:
${params.namespace}_logs:
external: true
# 使用外部已存在的卷(不自动创建)
"""
}
// ============================================================================
// docker_service_param 函数:生成服务部署参数
// ============================================================================
def docker_service_param(module, jsonOption) {
def ipHostnameMap = [
'192.168.3.132': 'ZD-BAK-APP2',
'192.168.3.133': 'zd-bak-app3',
'192.168.3.134': 'ZD-BAK-APP1',
]
// IP 到主机名的映射表(Review 环境)
// 用于 Swarm 部署时的节点约束(placement constraints)
// 节点信息:
// - ZD-BAK-APP1 (192.168.3.134): Worker 节点
// - ZD-BAK-APP2 (192.168.3.132): Manager 节点 (Leader)
// - zd-bak-app3 (192.168.3.133): Worker 节点
def projectName = module.projectName
// 项目名称(如 sa-gateway)
def node = module.node
// 副本数量配置
def cpu = module.cpu.split("-")
// CPU 配置,格式 "预留-限制",如 "0.5-2"
// cpu[0] = 预留值, cpu[1] = 限制值
def memory = module.memory.split("-")
// 内存配置,格式 "预留-限制",如 "512M-2G"
// memory[0] = 预留值, memory[1] = 限制值
def address = module.address.split("\n")
// 部署地址配置,格式 "IP:端口",多个地址换行分隔
// 如:
// 192.168.3.132:18080
// 192.168.3.133:18080
def services = []
// 服务列表(一个模块可能部署到多台服务器)
for (final def add in address) {
// 遍历每个部署地址
def addSplit = add.split(":")
def ip = addSplit[0]
// 服务器 IP
def port = addSplit[1]
// 服务端口
def hostname = ipHostnameMap.get(ip)
// 通过 IP 查找对应的主机名
def serviceName = """ss${ip.split("\\.")[3]}_${projectName}"""
// 服务名生成规则:ss + IP最后一段 + _ + 项目名
// 如 192.168.3.132 + sa-gateway -> ss132_sa-gateway
def par = """-Dspring.cloud.inetutils.preferredNetworks=10.18"""
// JVM 参数:指定 Spring Cloud 优先使用的网络段
// 10.18 是 Review 环境 Docker Overlay 网络的子网段
// 确保服务注册到 Nacos 时使用正确的 IP 地址
services.add([
nacos_address : jsonOption.nacos_address,
// Nacos 集群地址
namespace : jsonOption.namespace,
// 命名空间
projectName : projectName,
// 项目名
IMAGE : "$jsonOption.harbor/sa-server/$projectName:$module.imageTag",
// 完整镜像地址,如 harbor.sino-assist.com/sa-server/sa-gateway:master
profile : jsonOption.profile,
// Spring 环境
node : node,
// 副本数量
reservationsCpu : cpu[0],
// CPU 预留值
limitCpu : cpu[1],
// CPU 限制值
reservationsMemory: memory[0],
// 内存预留值
limitMemory : memory[1],
// 内存限制值
serviceName : serviceName,
// 服务名(Stack 名称)
hostname : hostname,
// 目标主机名(节点约束)
port : port,
// 服务端口
nativeIp : ip,
// 部署目标 IP
params : par
// 额外 JVM 参数
])
}
echo "params ${params}"
// 打印参数信息(调试用)
return services
// 返回服务列表
}
// vim: ft=groovy
// 告诉 vim 编辑器使用 groovy 语法高亮
23.6 修改记录
| 日期 | 修改内容 |
|---|---|
| 2025-12-23 | 初始化 Review 环境配置:namespace (prod → review)、SSH 用户 (sasys → root)、IP 映射表、网络子网 (10.17 → 10.18) |
| 2025-12-23 | 移除冗余 SkyWalking 配置:SkyWalking 相关配置已在 build.gradle 的 Jib entrypoint 中设置,无需在部署时重复配置环境变量 |
23.7 Jenkins 配置要求
在 Jenkins 中使用此脚本需要以下配置:
凭据配置
| 凭据 ID | 类型 | 用途 |
|---|---|---|
| harbor | Username with password | Harbor 镜像仓库登录 |
| gitlab | Username with password | GitLab 代码仓库访问 |
全局工具配置
| 工具名 | 类型 | 说明 |
|---|---|---|
| gradle | Gradle | Gradle 构建工具路径 |
参数化构建配置
| 参数名 | 类型 | 说明 |
|---|---|---|
| branch | String/Git Parameter | Git 分支名 |
| modulesOption | String | JSON 格式的模块配置 |
modulesOption JSON 格式示例
{
"deploy_modules": [
{
"module": "sa-gateway",
"o": true,
"node": 1,
"cpu": "0.5-2",
"memory": "512M-2G",
"address": "192.168.3.132:18080"
}
],
"deploy_step": "打包镜像,部署服务"
}
二十四、前端服务部署 (sa-cc)
24.1 概述
前端服务 sa-cc 是 Vue 项目,部署采用 Docker Swarm Stack 方式。
24.2 Jenkins Pipeline 脚本
脚本文件:docker-swarm-review/pipeline-script-cc
配置说明
| 配置项 | Review 值 | 说明 |
|---|---|---|
| deploy_server | 192.168.3.132 | 部署目标服务器 |
| profile | review | 环境标识 |
| NAMESPACE | review | Docker 网络命名空间 |
| REGISTRY_URL | harbor.sino-assist.com | 镜像仓库地址 |
| SSH 用户 | root | 远程执行用户 |
| SSH 密码 | Sino.2025 | 远程执行密码 |
脚本内容
#!/usr/bin/env groovy
def projectProperties = [
[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '5']],
parameters([
string(name: 'branch', description: '分支'),
booleanParam(name: 'yarnInstall', description: '是否更新node_modules'),
string(name: 'backUrl', defaultValue: 'https://api1.sino-assist.com', description: 'backend server url')
])
]
def deploy_server = "192.168.3.132"
def profile = "review"
def NAMESPACE = "review"
def REGISTRY_URL = "harbor.sino-assist.com"
node {
def workspace = pwd()
def DOCKER_CREDENTIAL_ID = 'harbor'
stage('checkout') {
git branch: branch, credentialsId: 'gitlab', url: 'https://git.sino-assist.com/server/sa-cc.git'
}
if(params.build==true){
stage('build') {
nodejs(nodeJSInstallationName: 'nodejs-v22') {
if(params.yarnInstall == true){
sh "yarn"
}
sh "export NODE_OPTIONS=--openssl-legacy-provider"
String backUrl = params.backUrl
if(profile == 'prod'){
sh "sed -i 's|VUE_APP_BACK_REST_URL_PLACE_HOLDER|${backUrl}|' ${workspace}/.env.prod"
sh "yarn build-prod"
}else{
sh "sed -i 's|VUE_APP_BACK_REST_URL_PLACE_HOLDER|${backUrl}|' ${workspace}/.env.alpha"
sh "yarn build"
}
}
}
stage('docker-login') {
withCredentials([usernamePassword(passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME', credentialsId: "${DOCKER_CREDENTIAL_ID}",)]) {
sh "echo '$DOCKER_PASSWORD' | docker login ${REGISTRY_URL} -u '$DOCKER_USERNAME' --password-stdin"
}
}
stage('docker-build') {
sh " docker build -f k8s/Dockerfile -t ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE} ."
}
stage('docker-push') {
sh "docker push ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE}"
}
}
stage('deploy') {
String yml = """
version: \\"3.8\\"
services:
svc:
image: ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE}
ports:
- '8081:8080'
environment:
- TZ=Asia/Shanghai
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
order: start-first
resources:
limits:
cpus: \\"1\\"
memory: "300M"
reservations:
cpus: \\"4\\"
memory: "4G"
placement:
constraints:
- "node.labels.${NAMESPACE}_sa-cc==1"
networks:
default:
name: ${NAMESPACE}
external: true
"""
String serverName = "${NAMESPACE}_ss_sa-cc"
String ymlFile = "/data/swarm/${serverName}.yml"
String deploy = "sshpass -p 'Sino.2025' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@${deploy_server} \" mkdir -p /data/swarm/ && echo '''${yml}''' > ${ymlFile} && docker stack deploy -c ${ymlFile} ${serverName} --prune --with-registry-auth\""
echo deploy
sh deploy
}
}
24.3 部署前准备
添加节点标签
前端服务通过节点标签约束部署位置,需要先在目标节点添加标签:
# 查看集群节点
docker node ls
# 在目标节点添加标签(示例:部署到 132 节点)
docker node update --label-add review_sa-cc=1 ZD-BAK-APP2
24.4 Jenkins 配置
参数化构建配置
| 参数名 | 类型 | 说明 |
|---|---|---|
| branch | String | Git 分支名 |
| build | Boolean | 是否执行构建(可只部署不构建) |
| yarnInstall | Boolean | 是否更新 node_modules |
| backUrl | String | 后端 API 地址 |
24.5 访问验证
部署完成后,前端服务监听 8081 端口:
http://192.168.3.132:8081
二十五、Nginx 反向代理(解决跨域)
25.1 问题背景
直接通过 IP:端口 访问前端时,前端 (8081) 调用后端 (28092) 会因端口不同触发浏览器跨域限制:
Access to XMLHttpRequest at 'http://192.168.3.132:28092/common/auth/token'
from origin 'http://192.168.3.132:8081' has been blocked by CORS policy
同源策略:协议 + 域名/IP + 端口 三者必须完全相同,端口不同即为跨域。
25.2 解决方案
部署 Nginx 反向代理,将前后端统一到同一端口:
http://192.168.3.132:8080
├── / → 前端 (8081)
└── /api路径/ → 后端网关 (28092)
25.3 部署步骤
25.3.1 创建配置目录
mkdir -p /data/nginx-proxy
25.3.2 创建 Nginx 配置文件
cat > /data/nginx-proxy/nginx.conf << 'EOF'
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 100M;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# 18092:接收来自 3.110 nginx 的 apireview 转发请求,记录日志后再转发到网关
server {
listen 18092;
access_log /var/log/nginx/access.log main;
location / {
proxy_pass http://192.168.3.132:28092;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 8080;
# 前端
location / {
proxy_pass http://192.168.3.132:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 后端网关 - 所有 API 请求
location ~ ^/(common|order|supplier|contract|base|export-app|auth|user|system|api|ws|return|returnVehicle|returnOrder|supplierManage|agg-api|zgs|gps|data-search)/ {
proxy_pass http://192.168.3.132:28092;
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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
EOF
25.3.3 启动 Nginx 容器
mkdir -p /data/nginx-proxy/logs
docker run -d \
--name nginx-proxy \
--restart always \
-p 8080:8080 \
-p 18092:18092 \
-v /data/nginx-proxy/nginx.conf:/etc/nginx/nginx.conf:ro \
-v /data/nginx-proxy/logs:/var/log/nginx \
nginx:alpine
25.3.4 配置日志按天分割(logrotate)
日志写入宿主机 /data/nginx-proxy/logs/,由 logrotate 每天切割,保留 90 天(约3个月)。
cat > /etc/logrotate.d/nginx-proxy << 'EOF'
/data/nginx-proxy/logs/*.log {
daily
rotate 90
dateext
dateformat -%Y%m%d
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
docker kill --signal=USR1 nginx-proxy 2>/dev/null || true
endscript
}
EOF
验证配置并手动执行一次:
# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-proxy
# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-proxy
logrotate 默认由系统 cron 每天凌晨自动执行(
/etc/cron.daily/logrotate),无需额外配置定时任务。
25.3.5 nginx-review-132 日志按天分割(logrotate)
nginx-review-132 通过 Docker Swarm 部署在 ZD-BAK-APP2 节点上,日志挂载在宿主机 /opt/logs/nginx/,在该节点上执行以下配置。
cat > /etc/logrotate.d/nginx-review << 'EOF'
/opt/logs/nginx/*.log {
daily
rotate 90
dateext
dateformat -%Y%m%d
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
docker kill --signal=USR1 $(docker ps -qf name=nginx-review) 2>/dev/null || true
endscript
}
EOF
验证配置并手动执行一次:
# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-review
# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-review
25.3.6 生产环境 nginx 日志按天分割(logrotate)
生产 nginx(prod_nginx_server1)部署在 ZD-CRM1,日志挂载在宿主机 /opt/logs/nginx/。需在 ZD-CRM1 和 ZD-CRM2 两台节点上分别执行(prod_nginx_server2 后续恢复后同样生效)。
注意:
/opt/logs/目录权限为 777,需在配置中加su root sasys指令,否则 logrotate 会因权限问题跳过。
cat > /etc/logrotate.d/nginx-prod << 'EOF'
/opt/logs/nginx/*.log {
su root sasys
daily
rotate 90
dateext
dateformat -%Y%m%d
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
docker kill --signal=USR1 $(docker ps -qf name=prod_nginx) 2>/dev/null || true
endscript
}
EOF
验证配置并手动执行一次:
# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-prod
# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-prod
25.3.7 常见问题排查
问题一:logrotate 显示已执行但无归档文件生成
现象:/var/lib/logrotate/logrotate.status 中有记录,但 /opt/logs/nginx/ 目录下没有 .gz 归档文件。
原因:logrotate 首次执行时日志文件因长期权限问题积压过大,压缩超时或 IO 中断导致归档失败,但 status 已写入,后续每天判断"今天已转过"而跳过。
排查命令:
# 查看 status 记录
cat /var/lib/logrotate/logrotate.status | grep nginx
# 查看 cron 执行日志
grep logrotate /var/log/cron | tail -10
问题二:logrotate 执行后 nginx 未切换到新日志文件(仍写入 access.log-old)
现象:发送 USR1 信号后,新的 access.log 没有日志写入,nginx 仍在写旧文件。
原因:nginx 容器内进程以 uid 1001 运行,tail -c 生成的临时文件默认属主为 root,1001 无写入权限,USR1 后重新 open 失败,继续持有旧文件描述符。
解决:
# 修正新 access.log 的权限
chmod 777 /opt/logs/nginx/access.log
# 重新发送 USR1
docker kill --signal=USR1 $(docker ps -qf name=prod_nginx)
# 确认已切换
tail -f /opt/logs/nginx/access.log
问题三:日志文件积压过大(数百 GB),需手动清理后重新切割
适用场景:logrotate 长期未生效导致 access.log 积压,需保留最近部分数据后重新切割。
# 1. 截取最后 10G 到临时文件(保留近期数据)
tail -c 10G /opt/logs/nginx/access.log > /opt/logs/nginx/access.log.tmp
# 2. 原子替换(nginx 此时仍写旧文件,不中断)
mv /opt/logs/nginx/access.log /opt/logs/nginx/access.log-old
mv /opt/logs/nginx/access.log.tmp /opt/logs/nginx/access.log
# 3. 修正权限(nginx 以 1001 运行,需可写)
chmod 777 /opt/logs/nginx/access.log
# 4. 发送 USR1,让 nginx 重新打开文件
docker kill --signal=USR1 $(docker ps -qf name=prod_nginx)
# 5. 确认 nginx 已切换到新文件
tail -f /opt/logs/nginx/access.log
# 6. 确认后删除旧的积压文件
rm /opt/logs/nginx/access.log-old
# 7. 强制执行 logrotate,重新建立切割基准
logrotate -f /etc/logrotate.d/nginx-prod
注意:
mv是原子操作,nginx 在收到USR1之前始终持有旧文件的文件描述符,文件被mv走不会中断写入,USR1之后才重新open新的access.log。
25.3.8 将 3.110 宿主机 nginx 迁移到 3.132(Swarm)
背景:原 3.110 为宿主机直接安装的 nginx,承载所有对外域名的反向代理(CRM1/CRM2/Review/UAT/Pay 等)。迁移目标:将全部配置合并到 3.132 的 Docker Swarm nginx service,去除 3.110→3.132:18092 的中转层,直连后端网关。
配置文件位置:docker-swarm-review/nginx-review-132/swarm/
swarm/
├── nginx.conf # 主配置
├── ssl.sino_assist.conf # sino-assist.com 证书 include(23368363 目录)
├── ssl.conf # sinoassist.com 证书 include(2026 目录)
├── sites/
│ ├── crm1.conf # crm1/api1/api-sit/portainer/oem-jlr
│ ├── crm2.conf # api2/crm2/cc.crm2/stomp2/test-api
│ ├── review.conf # apireview/ccreview/jenkins(直连 28092,无中转)
│ ├── fastdfs.conf # file-gk(18888/18889)
│ ├── pay.conf # pay/pay-manager/pay-client
│ ├── wx.conf # supplierwxtest/site.sinoassist
│ ├── git.conf # jira/itsm/wiki/vote/harbor/git/maven 等
│ ├── uat.conf # uat/api-uat/api-pre
│ └── zd_report.conf # report/bi.sino-assist/bi.sinoassist
├── docker-compose.yml
└── logrotate-nginx
部署步骤:
1. 在 ZD-BAK-APP2 上创建目录并上传文件
mkdir -p /data/nginx/sites
mkdir -p /data/nginx/ssl/23368363_sino-assist.com_nginx
mkdir -p /data/nginx/ssl/2026
mkdir -p /opt/logs/nginx
# 上传文件:
# nginx.conf、ssl.sino_assist.conf、ssl.conf → /data/nginx/
# sites/*.conf → /data/nginx/sites/
# 证书文件 → /data/nginx/ssl/ 对应目录
# docker-compose.yml、logrotate-nginx → /data/nginx/
2. 部署 Swarm service(在 manager 节点执行)
docker stack deploy -c /data/nginx/docker-compose.yml nginx-review
验证:
docker service ls | grep nginx-review
docker service ps nginx-review_nginx-review
3. 验证各域名转发正常
# review 接口(返回 200 或 4xx 均表示 nginx 转发正常)
curl -k -o /dev/null -s -w "%{http_code}" --resolve "apireview.sino-assist.com:443:192.168.3.132" https://apireview.sino-assist.com/common/auth/token
# review 前端
curl -k -o /dev/null -s -w "%{http_code}" --resolve "ccreview.sino-assist.com:443:192.168.3.132" https://ccreview.sino-assist.com/
4. 配置 logrotate(在 ZD-BAK-APP2 上执行)
cp /data/nginx/logrotate-nginx /etc/logrotate.d/nginx-review
logrotate -d /etc/logrotate.d/nginx-review # 验证
logrotate -f /etc/logrotate.d/nginx-review # 手动执行一次建立基准
注意:ZD-BAK-APP2 上无
sasys组,/opt/logs/nginx/属主为 root,logrotate 配置中不需要su root sasys指令。
5. DNS 切流
联系 DNS 负责人,将所有域名 A 记录从 192.168.3.110 改为 192.168.3.132。
建议分批切流:
- Review 环境(
apireview、ccreview) - 内网工具(
jira、wiki、harbor、git等) - 生产核心(
crm1、api2、crm2、uat)
6. 停止 3.132 旧的 nginx-proxy 容器
DNS 切流并确认所有域名访问正常后执行:
docker stop nginx-proxy && docker rm nginx-proxy
7. 迁移验证
DNS 切流前,可通过本地 hosts 绑定提前验证(Windows:C:\Windows\System32\drivers\etc\hosts):
192.168.3.132 apireview.sino-assist.com
192.168.3.132 ccreview.sino-assist.com
192.168.3.132 crm1.sino-assist.com
192.168.3.132 api2.sino-assist.com
192.168.3.132 crm2.sino-assist.com
192.168.3.132 uat.sino-assist.com
# 其他需要验证的域名...
验证地址清单:
| 域名 | 测试地址 | 期望结果 |
|---|---|---|
| Review 接口 | https://apireview.sino-assist.com/common/auth/token |
200 |
| Review 前端 | https://ccreview.sino-assist.com/ |
200 |
| CRM1 | https://crm1.sino-assist.com/ |
200 |
| CRM2 接口 | https://api2.sino-assist.com/common/auth/token |
200 |
| CRM2 前端 | https://crm2.sino-assist.com/ |
200/301 |
| UAT | https://uat.sino-assist.com/common/auth/token |
200 |
| Portainer | https://portainer.sino-assist.com/ |
200 |
| Git | https://git.sino-assist.com/ |
200 |
| Harbor | https://harbor.sino-assist.com/ |
200 |
| Report | https://report.sino-assist.com/ |
200 |
注意:根路径
/返回 404 属正常现象(Spring Boot 网关无根路由),需测试实际接口路径如/common/auth/token。
验证完成后删除 hosts 中的临时绑定记录。
DNS 切流后,在 3.132 上确认日志有请求写入,同时确认 3.110 上 nginx 日志停止增长:
# 3.132 上
tail -f /opt/logs/nginx/access.log
# 3.110 上
tail -f /zd/src/nginx/logs/access.log
25.4 访问验证
部署完成后,通过统一入口访问:
http://192.168.3.132:8080
25.5 配置更新
如需添加新的后端 API 路径,修改 /data/nginx-proxy/nginx.conf 中的正则表达式:
location ~ ^/(common|order|新路径1|新路径2)/ {
然后重启容器:
docker restart nginx-proxy
25.6 当前支持的 API 路径
| 路径前缀 | 说明 |
|---|---|
| common | 通用服务 |
| order | 订单服务 |
| supplier | 供应商服务 |
| contract | 合同服务 |
| base | 基础服务 |
| export-app | 导出服务 |
| auth | 认证服务 |
| user | 用户服务 |
| system | 系统服务 |
| api | 通用 API |
| ws | WebSocket |
| return | 返程服务 |
| returnVehicle | 返程车辆 |
| returnOrder | 返程订单 |
| supplierManage | 供应商管理 |
| agg-api | 聚合 API |
| zgs | 中钢服务 |
| gps | GPS 服务 |
| data-search | 数据搜索 |
25.7 常见问题
问题 1:504 Gateway Timeout
后端响应超时,可在 location 块中添加:
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
问题 2:WebSocket 连接失败
确保已配置 Upgrade 头:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
问题 3:新增 API 路径 404
检查 location 正则是否包含该路径,更新配置后重启容器。
25.9 nginx-review-132 变更记录
25.9.1 新增 oss.sinoassist.com 路由(2026-04-29)
新增 sites/oss.conf,将 oss.sinoassist.com 的请求直接转发到 3.125(ZD-FDFS4)的 file-oss 服务,不再经过 3.132:28092 网关。
# /data/nginx/sites/oss.conf
server {
listen 80;
server_name oss.sinoassist.com;
include /etc/nginx/ssl.sino_assist.conf;
location / {
proxy_pass http://192.168.3.125:25773;
proxy_set_header X-Forwarded-Host $server_name;
if ($request_filename ~ .*\.(htm|html)$) {
add_header Cache-Control no-cache;
}
}
}
在 3.132 上创建文件后热重载生效,无需重新部署:
docker exec $(docker ps --filter name=nginx-review -q) nginx -t
docker exec $(docker ps --filter name=nginx-review -q) nginx -s reload
25.9.2 修复日志 IP 显示为 10.0.0.2 及时区问题(2026-04-29)
问题: 日志中客户端 IP 全部显示为 10.0.0.2(Swarm ingress IPVS 地址),时间为 UTC 无 +8。
原因: 端口以默认 ingress 模式发布时,流量经过 Swarm ingress 网络的 IPVS 转发,真实源 IP 在到达容器前已被替换,无法通过 log_format 或 X-Forwarded-For 恢复。
解决:
- 端口发布模式改为
mode: host,绕过 ingress 网络,nginx 直接收到真实客户端 IP - 新增
TZ=Asia/Shanghai环境变量修正时区
docker-compose.yml 关键变更:
services:
nginx-review:
environment:
- TZ=Asia/Shanghai
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
# 其他端口同样改为 mode: host
注意:
mode: host下docker service ls不显示端口映射,属正常现象,实际端口仍正常监听。
重新部署生效(会有秒级中断):
cd /opt/swarm/support/nginx-review-132
docker stack deploy --compose-file docker-compose.yml nginx-review
25.9.3 修复 crm1/crm2 切换后域名访问 404(2026-04-29)
问题: DNS 从 3.110 切到 3.132 后,crm1.sino-assist.com 访问返回 Whitelabel 404,crm2 同理。
根因: 192.168.1.209:8080 后端会根据请求的 Host 头决定返回前端页面还是 404。nginx 有一个隐性规则:location 块内只要出现任何 proxy_set_header,就会覆盖 http 块全局定义的所有 proxy_set_header。3.132 的 crm1.conf 中 location 块有 proxy_set_header X-Forwarded-Host,导致全局的 proxy_set_header Host $host 被覆盖,Host 头未传递到后端,后端无法识别域名返回 404。
排查过程:
# 3.110 走域名返回 200 html,3.132 走域名返回 404 json
curl -v --resolve "crm1.sino-assist.com:443:192.168.3.110" https://crm1.sino-assist.com/ -k -s -w "%{http_code}\n" # 200
curl -v --resolve "crm1.sino-assist.com:443:192.168.3.132" https://crm1.sino-assist.com/ -k -s -w "%{http_code}\n" # 404
# 带 Host 头直接访问后端,返回正常 html
curl -s -H "Host: crm1.sino-assist.com" http://192.168.1.209:8080/ | head -5 # 返回 html
修复: 在 crm1.conf 和 crm2.conf 中所有转发到 192.168.1.209 的 location 块内补全 proxy_set_header Host $host:
location / {
proxy_pass http://192.168.1.209:8080/;
proxy_set_header Host $host; # 补充此行
proxy_set_header X-Forwarded-Host $server_name;
...
}
生效方式(无中断):
# 将修改后的 crm1.conf、crm2.conf 上传到 /data/nginx/sites/ 后执行
docker exec $(docker ps --filter name=nginx-review -q) nginx -t
docker exec $(docker ps --filter name=nginx-review -q) nginx -s reload
注意:凡是 location 块内有自定义
proxy_set_header的,必须同时显式声明proxy_set_header Host $host,否则全局设置会被静默覆盖。
25.9.4 修复 uat 域名访问 404(2026-05-07)
问题: uat.sino-assist.com 访问返回 404。
根因: 与 25.9.3 同一问题。uat.conf 所有 location 块只有 proxy_set_header X-Forwarded-Host,没有 proxy_set_header Host $host,导致全局 Host 头被覆盖,后端无法识别域名。
修复: 在 uat.conf 的所有 location 块内补全 proxy_set_header Host $host;,涉及以下四个 location:/、/h5/supplier/dispatch、/h5/client、/dev/h5/rentCar。
生效方式(无中断):
docker exec $(docker ps --filter name=nginx-review -q) nginx -t
docker exec $(docker ps --filter name=nginx-review -q) nginx -s reload
25.9.5 新增 fastdfs stream 层,支持 8888/38888 端口 HTTP/HTTPS 自动路由(2026-05-07)
背景: fastdfs 需要通过单一端口同时支持 HTTP 和 HTTPS 访问,原 fastdfs.conf 分别监听 18888(HTTP)和 18889(HTTPS),外部无法通过同一端口区分协议。
方案: 在 nginx.conf 追加 stream {} 块,利用 ssl_preread 在 TCP 层识别协议,将 8888/38888 的流量自动路由到 18888 或 18889:
stream {
upstream http_gateway {
server 127.0.0.1:18888;
}
upstream https_gateway {
server 127.0.0.1:18889;
}
map $ssl_preread_protocol $upstream {
default http_gateway;
"TLSv1.0" https_gateway;
"TLSv1.1" https_gateway;
"TLSv1.2" https_gateway;
"TLSv1.3" https_gateway;
}
server {
listen 8888;
listen 38888;
ssl_preread on;
proxy_pass $upstream;
}
}
流量路径:
客户端 → 8888 或 38888
├── HTTP → 127.0.0.1:18888 → fastdfs.conf server (listen 18888)
└── HTTPS → 127.0.0.1:18889 → fastdfs.conf server (listen 18889 ssl)
同步在 docker-compose.yml 新增 38888 端口映射(mode: host)。
生效方式(有秒级中断):
cd /opt/swarm/support/nginx-review-132
docker stack deploy --compose-file docker-compose.yml nginx-review
验证:
# 验证 8888 HTTP 转发
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.132:8888/
# 验证 38888
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.132:38888/
# 验证 nginx 转发到后端 3.119:8888
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.119:8888/
二十六、部署进度更新
26.1 已完成服务
| 阶段 | 状态 |
|---|---|
| 系统准备 | ✅ 完成 |
| 数据盘挂载 | ✅ 完成 |
| Docker 安装 | ✅ 完成 |
| Swarm 初始化 | ✅ 完成 |
| Overlay 网络 | ✅ 完成 |
| Portainer | ✅ 完成 |
| MySQL 主从复制 (mysql-repl-tool) | ✅ 完成 |
| RabbitMQ 集群 | ✅ 完成 |
| Nacos 集群 | ✅ 完成 |
| XXL-Job-Admin | ✅ 完成 |
| Canal | ✅ 完成 |
| Elasticsearch + Kibana | ✅ 完成 |
| Log (Logstash + Filebeat) | ✅ 完成 |
| SkyWalking | ✅ 完成 |
| MongoDB | ✅ 完成 |
| Redis Sentinel 集群 | ✅ 完成 |
| 后端服务 (sa-server) | ✅ 完成 |
| 前端服务 (sa-cc) | ✅ 完成 |
| Nginx 反向代理 | ✅ 完成 |
26.2 修改记录
| 日期 | 修改内容 |
|---|---|
| 2025-12-23 | 初始化 Review 环境配置 |
| 2025-12-25 | 新增前端服务 (sa-cc) 部署章节 |
| 2025-12-25 | 新增 Nginx 反向代理章节,解决跨域问题 |
| 2025-12-25 | 新增 Redis 故障切换与回滚策略章节 |
| 2026-04-29 | 新增节点 ZD-FDFS4 (192.168.3.125) 作为 Worker 节点 |
| 2026-04-29 | 新增 oss.sinoassist.com 路由,直连 3.125:25773,不经过网关 |
| 2026-04-29 | nginx 端口改为 host 模式修复日志 IP,新增 TZ=Asia/Shanghai 修正时区 |
| 2026-04-29 | 修复 crm1/crm2 切换后 404:location 块补全 proxy_set_header Host $host |
| 2026-05-07 | 修复 uat 域名 404:uat.conf 所有 location 块补全 proxy_set_header Host $host |
| 2026-05-07 | 新增 fastdfs stream 层:nginx.conf 追加 stream{},8888/38888 端口按协议自动路由到 18888/18889 |
| 2026-05-07 | docker-compose.yml 新增 38888 端口映射(mode: host) |
二十七、Redis 故障切换与回滚策略
27.1 架构背景
Review 环境的 Redis 采用级联复制架构,作为生产环境的灾备方案:
正常运行时(级联复制):
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 生产 10.56 │────▶│ Review 132 │────▶│ Review 133 │
│ (Master) │ │ (隐藏 Slave) │ │ (二级 Slave) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Review 134 │
│ (二级 Slave) │
└─────────────────┘
故障切换后(独立运行):
┌─────────────────┐ ┌─────────────────┐
│ Review 132 │────▶│ Review 133 │
│ (Master) │ │ (Slave) │
└─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Review 134 │
│ (Slave) │
└─────────────────┘
+
Review Sentinel 集群监控
27.2 节点信息
| 节点 | IP | 正常角色 | 故障切换后角色 |
|---|---|---|---|
| 生产主节点 | 192.168.10.56 | Master | - |
| Review 节点1 | 192.168.3.132 | 隐藏 Slave | Master |
| Review 节点2 | 192.168.3.133 | 二级 Slave | Slave |
| Review 节点3 | 192.168.3.134 | 二级 Slave | Slave |
27.3 故障切换脚本
脚本文件:docker-swarm-review/redis-review-132/failover-to-review.sh
命令说明
| 命令 | 功能 | 使用场景 |
|---|---|---|
status |
查看所有节点状态 | 日常检查、故障排查 |
failover |
提升 132 为 Master | 生产故障时 |
sentinel |
部署 Review Sentinel | 单独部署 Sentinel |
full |
完整切换 (failover + sentinel) | 生产故障时(推荐) |
rollback |
安全回滚 | 生产恢复后 |
unsafe-rollback |
不安全回滚 | 紧急情况(可能丢数据) |
可选参数
| 参数 | 说明 |
|---|---|
-y / --yes |
自动确认普通提示 |
--force |
强制模式,跳过所有确认(慎用) |
27.4 故障切换流程
当生产 Redis 故障时,执行以下步骤:
步骤 1:检查状态
cd /path/to/redis-review-132
./failover-to-review.sh status
输出示例:
========================================
当前 Redis 状态
========================================
[192.168.3.132]
角色: slave
主节点: 192.168.10.56:6379
同步状态: down # 生产故障时显示 down
[192.168.3.133]
角色: slave
主节点: 192.168.3.132:6379
同步状态: up
[192.168.3.134]
角色: slave
主节点: 192.168.3.132:6379
同步状态: up
步骤 2:执行完整切换
./failover-to-review.sh full
脚本会自动执行:
-
提升 132 为 Master
SLAVEOF NO ONE -
配置 133/134 指向 132
SLAVEOF 192.168.3.132 6379 -
部署 Review Sentinel
- 监控名称:
reviewmaster - Sentinel 端口:26379
- 监控名称:
步骤 3:修改应用配置
切换应用连接到 Review Sentinel:
# Spring Boot 配置
spring:
redis:
sentinel:
master: reviewmaster
nodes: 192.168.3.132:26379,192.168.3.133:26379,192.168.3.134:26379
password: sino#650
password: sino#650
或直连模式:
spring:
redis:
host: 192.168.3.132
port: 6379
password: sino#650
27.5 安全回滚流程
当生产 Redis 恢复后,需要将数据同步回生产并恢复原有架构。
回滚前提条件
- ✅ 132 当前是 Master(处于故障切换状态)
- ✅ 生产 10.56 已恢复并可连接
- ✅ 所有应用已切换到 Review Sentinel
- ✅ 生产 Sentinel 已停止
步骤 1:停止生产 Sentinel(手动)
重要:必须先在生产服务器上执行!
# SSH 到生产服务器
ssh root@192.168.10.56
# 停止生产 Sentinel
docker stack rm prod_sentinel
原因:
- 回滚步骤会让 10.56 临时变成 132 的从节点
- 如果 Sentinel 还在运行,会检测到 master 下线并触发故障转移
- 可能导致脑裂和数据不一致
步骤 2:执行安全回滚
./failover-to-review.sh rollback
脚本会执行以下步骤:
2.1 配置 10.56 作为 132 的从节点(反向同步)
# 在 10.56 执行
SLAVEOF 192.168.3.132 6379
2.2 等待数据同步完成
- 监控
master_repl_offset和slave_repl_offset - 偏移量差异 < 100 字节时自动继续
- 建议此时停止 Review 应用对 Redis 的写入
2.3 切换主从关系
# 将 132 设为只读,防止切换期间数据写入
CONFIG SET min-replicas-to-write 99
# 提升 10.56 为 Master
# 在 10.56 执行
SLAVEOF NO ONE
# 132 重新指向 10.56
SLAVEOF 192.168.10.56 6379
# 恢复 132 隐藏配置
CONFIG SET replica-announced no
CONFIG SET replica-priority 0
# 133/134 指向 132
SLAVEOF 192.168.3.132 6379
步骤 3:重启生产 Sentinel
# 在生产服务器执行
docker stack deploy -c sentinel-compose.yml prod_sentinel
步骤 4:修改应用配置
将应用连接切换回生产 Sentinel。
27.6 数据一致性保证
| 阶段 | 措施 | 说明 |
|---|---|---|
| 回滚前 | 反向同步 | 10.56 先作为 132 的从节点,同步故障期间的数据 |
| 回滚中 | 132 只读 | min-replicas-to-write=99 防止切换期间写入 |
| 回滚后 | 偏移量验证 | 确认数据完全同步 |
27.7 不安全回滚(不推荐)
如果紧急情况需要快速恢复,可以使用不安全回滚:
./failover-to-review.sh unsafe-rollback
警告:
- 直接让 132 重新指向 10.56
- 故障期间 132 上的所有写入数据将被覆盖
- 仅在确认故障期间无重要数据写入时使用
27.8 常见问题
问题 1:无法连接生产 Redis
生产环境 192.168.10.56:6379 无法连接
解决:检查生产 Redis 是否已恢复,网络是否通畅。
问题 2:同步超时
同步超时,请检查网络和 Redis 状态
解决:
- 检查网络连通性
- 检查 Redis 日志
- 可选择强制继续(有数据丢失风险)
问题 3:脚本无法远程执行命令
脚本会提示手动在生产服务器执行命令,按提示操作即可。
27.9 操作检查清单
故障切换检查清单
- 确认生产 Redis 确实故障
- 执行
./failover-to-review.sh status检查状态 - 执行
./failover-to-review.sh full完成切换 - 验证 132 已成为 Master
- 验证 Sentinel 正常运行
- 修改应用配置连接 Review Sentinel
- 验证应用功能正常
安全回滚检查清单
- 确认生产 Redis 已恢复
- 确认所有应用已切换到 Review Sentinel
- 在生产服务器停止 Sentinel:
docker stack rm prod_sentinel - 建议停止 Review 应用对 Redis 的写入
- 执行
./failover-to-review.sh rollback - 等待数据同步完成
- 验证回滚成功
- 重启生产 Sentinel
- 修改应用配置连接生产 Sentinel
- 验证应用功能正常