Files
sa-charts/docker-swarm-review/部署手册.md
2026-05-13 15:27:39 +08:00

6622 lines
197 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 源
```bash
# 备份官方源
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 源
```bash
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 查看磁盘信息
```bash
# 查看所有磁盘和分区
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 挂载数据盘
```bash
# 创建挂载点
mkdir -p /mnt/data
# 如果数据盘未格式化,先格式化(慎重!会清除数据)
# mkfs.ext4 /dev/sdb
# 临时挂载(重启后失效)
mount /dev/sdb /mnt/data
# 验证挂载
df
-h /mnt/data
```
#### 2.3.4 配置开机自动挂载
```bash
# 获取磁盘 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 创建服务数据目录
```bash
# 创建各服务数据目录
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 常见问题
**问题 1fstab 语法错误**
错误示例:
```
"/dev/sdb /mnt/data ext4 defaults 0 0" ← 整行被引号包裹(错误)
```
正确写法:
```
UUID=988dd535-6531-4a3e-89b7-104de26adcf2 /mnt/data ext4 defaults 0 0
```
**问题 2先部署服务后挂载数据盘**
如果在挂载数据盘之前部署了服务,数据会写入根分区。挂载数据盘后,原数据会被"遮盖"。
**解决方案:**
```bash
# 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 卸载旧版本
```bash
sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
```
### 3.2 安装依赖工具
```bash
sudo yum install -y yum-utils
```
### 3.3 配置 Docker 仓库
```bash
# 使用阿里云镜像源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
```
### 3.4 安装 Docker
```bash
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
### 3.5 配置 Docker Daemon
```bash
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
```bash
sudo systemctl daemon-reload
sudo systemctl start docker
sudo systemctl enable docker
# 验证
systemctl status docker
docker version
```
### 3.7 关闭防火墙
```bash
systemctl stop firewalld
systemctl disable firewalld
```
### 3.8 调整文件描述符限制
```bash
# 临时生效
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 初始化集群(主节点)
```bash
docker swarm init
```
### 4.2 验证节点
```bash
docker node ls
```
预期输出:
```
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
xxx * ZD-BAK-APP2 Ready Active Leader
```
---
## 五、创建 Overlay 网络
```bash
docker network create \
--driver=overlay \
--subnet=10.18.0.0/16 \
--scope swarm \
--attachable \
review
```
**验证:**
```bash
docker network ls | grep review
```
---
## 六、部署 Portainer
### 6.1 配置文件
`docker-compose.yml`:
```yaml
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 部署命令
```bash
cd /path/to/docker-swarm-review/portainer
docker stack deploy --compose-file docker-compose.yml portainer
```
### 6.3 验证
```bash
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 新节点准备工作
在新服务器上依次执行(同主节点):
1. 更换阿里云 YUM 源
2. 安装 Docker
3. 配置 Docker Daemon
4. 启动 Docker
5. 关闭防火墙
6. 调整文件描述符限制
### 7.3 获取加入 Token主节点执行
```bash
# 获取 Worker 加入 Token
docker swarm join-token worker
# 获取 Manager 加入 Token
docker swarm join-token manager
```
### 7.4 加入集群(新节点执行)
```bash
docker swarm join --token SWMTKN-1-xxxxx <主节点IP>:2377
```
### 7.5 验证节点(主节点执行)
```bash
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 节点标签管理
```bash
# 添加标签
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 节点管理命令
```bash
# 查看所有节点
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
```bash
# 从文件创建
docker config create <config名称> <文件路径>
# 示例
docker config create review_mysql_conf_v1 /tmp/mysql_custom.cnf
```
### 8.3 查看 Config
```bash
docker config ls
docker config inspect <config名称>
```
### 8.4 在 docker-compose.yml 中引用
```yaml
services:
myservice:
configs:
- source: my_config
target: /path/in/container/config.conf
configs:
my_config:
external: true
name: my_config_name
```
### 8.5 删除 Config
```bash
docker config rm <config名称>
```
> 注意:删除前需确保没有服务在使用该 Config。
---
## 九、Docker 挂载详解
### 9.1 Docker 挂载类型
```
┌─────────────────────────────────────────────────────────────────┐
│ Docker 挂载类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Bind Mount绑定挂载← 本环境使用的方式 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 宿主机 │ │ 容器 │ │
│ │ /mnt/data/ │ ═════► │ /bitnami/ │ │
│ │ volumes/ │ │ mongodb │ │
│ │ mongodb │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ - 直接映射宿主机目录到容器 │
│ - 路径由用户指定 │
│ - 适合需要在特定位置存储数据 │
│ │
│ 2. VolumeDocker 管理卷) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Docker 管理 │ │ 容器 │ │
│ │ /var/lib/ │ ═════► │ /bitnami/ │ │
│ │ docker/ │ │ mongodb │ │
│ │ volumes/xxx │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ - Docker 自动管理存储位置 │
│ - 默认在 /var/lib/docker/volumes/ │
│ - 便于 Docker 管理,但位置不可控 │
│ │
│ 3. tmpfs内存挂载
│ - 数据存储在内存中 │
│ - 容器停止后数据丢失 │
│ - 用于临时敏感数据 │
└─────────────────────────────────────────────────────────────────┘
```
### 9.2 docker-compose.yml 中的挂载配置
**Bind Mount 方式(本环境使用):**
```yaml
volumes:
- '/mnt/data/volumes/mongodb:/bitnami/mongodb'
# ↑ 宿主机路径 ↑ 容器内路径
```
**Volume 方式Docker 管理):**
```yaml
services:
db:
volumes:
- 'mongodb_data:/bitnami/mongodb'
volumes:
mongodb_data:
driver: local
```
### 9.3 验证挂载是否生效
#### 方法 1检查容器挂载信息
```bash
# 检查 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确认数据写入位置
```bash
# 查看宿主机目录是否有数据生成
ls -la /mnt/data/volumes/elasticsearch/
ls -la /mnt/data/volumes/mongodb/
# 查看数据大小
du -sh /mnt/data/volumes/*
```
#### 方法 3确认数据在数据盘上
```bash
# 确认路径属于 /dev/sdb3.7T 数据盘)
df -h /mnt/data/volumes/elasticsearch
df -h /mnt/data/volumes/mongodb
# 预期输出应显示 /dev/sdb 和 3.7T 容量
```
#### 方法 4写入测试文件验证
```bash
# 在宿主机创建测试文件
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更换镜像加速器**
```bash
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使用其他源拉取后打标签**
```bash
docker pull <其他源>/<镜像名>
docker tag <其他源>/<镜像名> <原镜像名>
```
**方案 3从其他机器导入镜像**
```bash
# 在能访问的机器上导出
docker save <镜像名> -o image.tar
# 传输到目标机器后导入
docker load -i image.tar
```
**方案 4通过私有仓库中转推荐**
如果有私有镜像仓库(如 Harbor可以先在能访问外网的服务器拉取镜像推送到私有仓库再从私有仓库拉取
```bash
# 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 镜像):
```bash
# 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 镜像标签打错处理
**问题:** 给镜像打标签时打错了(如版本号或仓库名写错)
**解决方案:**
**删除本地错误标签:**
```bash
# 删除错误的本地标签(只删除标签,不删除镜像本身)
docker rmi <错误的标签名>
# 示例:删除打错的标签
docker rmi harbor.sino-assist.com/bitnami/redis:wrong-version
# 然后重新打正确的标签
docker tag <原镜像名>:<版本> <正确的标签名>
```
**删除已推送到 Harbor 的错误标签:**
```bash
# 方法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>
```
**示例:**
```bash
# 场景:不小心把 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`
**解决:**
```bash
docker service update --force portainer_portainer
```
然后立即访问并设置管理员密码5 分钟内)。
### 10.4 iptables 规则错误
**问题:** `iptables: No chain/target/match by that name`
**解决:**
```bash
systemctl restart docker
```
### 10.5 EOF Heredoc 语法问题
**问题:** 多行内容写入文件时出现 `>` 等待符号
**原因:**
- 结束的 `EOF` 前面有空格或缩进
- Windows 与 Linux 换行符不一致
**解决:** 使用单行 echo 或 vim 手动创建:
```bash
echo '内容' > /path/to/file
```
或:
```bash
vim /path/to/file
# 粘贴内容后 :wq 保存
```
### 10.6 Windows 换行符问题
**问题:** `yaml: line xx: could not find expected ':'`
**解决:**
```bash
sed -i 's/\r$//' /path/to/file
```
### 10.7 服务状态为 Ready 不变为 Running
**问题:** `docker stack ps` 显示服务状态一直是 `Ready``Preparing`
**排查:**
```bash
docker stack ps <stack名称> --no-trunc
docker service logs <服务名>
```
**常见原因:**
- 镜像拉取失败
- 节点标签不匹配
- 资源不足
### 10.8 Swarm 跨节点服务报 "No such image"
**问题:** `docker stack ps` 显示 `Rejected: No such image`
**原因:** Swarm 跨节点部署时,目标节点上没有所需镜像,且无法从镜像仓库拉取。
**解决:** 在目标节点上手动拉取镜像:
```bash
# 方法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 失败导致容器重启。
**排查:**
```bash
# 查看日志确认是否被 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` 给足够的初始化时间:
```yaml
healthcheck:
test: ['CMD', '/opt/bitnami/scripts/mysql/healthcheck.sh']
interval: 30s
timeout: 10s
retries: 5
start_period: 180s
```
**方案 2删除 Slave 数据卷重新初始化**
如果 Slave 数据卷有旧数据,会跳过复制配置初始化:
```bash
docker stack rm <stack名称>
sleep 15
docker volume rm <slave数据卷名称>
# 重新部署
```
### 10.10 Swarm 节点间通信问题
**问题:** 跨节点的服务无法通过 overlay 网络通信
**排查:**
```bash
# 检查节点状态
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 <网络名> <镜像> <测试命令>
```
**解决:**
1. 确保所有节点防火墙已关闭:
```bash
systemctl stop firewalld
systemctl disable firewalld
```
2. 或者开放 Swarm 所需端口:
```bash
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
**排查:**
```bash
# 检查环境变量是否正确
docker service inspect <slave服务名> --pretty | grep -A 30 "Env"
# 查看初始化日志是否有复制配置
docker service logs <slave服务名> 2>&1 | grep -iE "repl|slave|master|configur"
```
**解决:**
```bash
# 删除 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`
**排查:**
```bash
# 检查 docker-compose.yml 中的镜像名
grep -i "image:" docker-compose.yml
```
**解决:** 修正镜像名称后重新部署
### 10.13 RabbitMQ 集群节点无法加入
**问题:** RabbitMQ 队列节点无法加入 stats 节点组成集群
**原因:**
- ERL_COOKIE 不一致
- 节点间网络不通
- stats 节点尚未启动完成
**排查:**
```bash
# 检查集群状态
docker exec $(docker ps -q -f name=rabbitmq_stats) rabbitmqctl cluster_status
# 检查节点日志
docker service logs <队列服务名> --tail 100
```
**解决:**
```bash
# 确保 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` 不为空
**原因:** 节点间网络中断后恢复,导致集群分裂
**解决:**
```bash
# 方法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 无法登录管理界面
**问题:** 部署成功但无法使用配置的用户名密码登录管理界面
**原因:**
- 用户不存在
- 密码不正确
- 用户没有管理员权限
**排查:**
```bash
# 查看用户列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users
```
**解决:**
```bash
# 如果用户不存在,创建用户
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 配置文件
```bash
# 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
```bash
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 跨节点部署时,每个节点都需要有镜像。
```bash
# 在每个节点上拉取镜像
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
```
#### 确保所有节点防火墙已关闭
```bash
# 在每个节点执行
systemctl stop firewalld
systemctl disable firewalld
```
### 10.3 部署命令
```bash
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 验证部署
```bash
# 查看服务状态
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 常用运维命令
```bash
# 查看服务日志
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 部署前准备
#### 添加节点标签
```bash
# 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}}'
```
#### 确保所有节点有镜像
```bash
# 在每个目标节点上执行
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/rabbitmq:3.11
```
### 11.4 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
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
```
#### 检查集群状态
```bash
# 进入 stats 节点检查集群状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl cluster_status
```
预期输出应包含:
- `Running Nodes`: rabbit@stats, rabbit@queue1, rabbit@queue2
- `Alarms`: (none)
- `Network Partitions`: (none)
#### 检查节点健康状态
```bash
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl node_health_check
```
#### 查看集群中的所有节点
```bash
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 用户管理
#### 查看用户列表
```bash
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users
```
#### 如果无法登录,重置密码
```bash
# 修改用户密码
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl change_password root <新密码>
```
#### 如果用户不存在,创建用户
```bash
# 创建用户
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 ".*" ".*" ".*"
```
#### 删除用户
```bash
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl delete_user <用户名>
```
### 11.8 常用运维命令
```bash
# 查看服务日志
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 数据库
```bash
# 连接 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
```bash
# 导入建表脚本
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' nacos < /path/to/nacos-cluser/mysql-schema.sql
```
#### 拉取镜像
在每个目标节点上拉取镜像:
```bash
docker pull nacos/nacos-server:v2.3.0
```
### 12.4 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
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 检查集群节点:
```bash
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 常用运维命令
```bash
# 查看服务日志
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
**原因:** 数据库连接失败或用户没有权限
**解决:**
```bash
# 检查数据库连接
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 端口未开放
**解决:**
```bash
# 确保防火墙已关闭
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 数据库
```bash
# 连接 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
```bash
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' xxl_job < tables_xxl_job.sql
```
#### 添加节点标签
```bash
# 选择 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}}'
```
#### 拉取镜像
在目标节点上拉取镜像:
```bash
docker pull xuxueli/xxl-job-admin:2.4.1
```
### 13.3 部署命令
```bash
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 验证部署
```bash
# 查看服务状态
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 常用运维命令
```bash
# 查看服务日志
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 数据库
**解决:**
```bash
# 检查数据库连接
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 已开启:
```sql
-- 检查 binlog 是否开启
SHOW VARIABLES LIKE 'log_bin';
-- 检查 binlog 格式(需要为 ROW
SHOW VARIABLES LIKE 'binlog_format';
```
#### 确保 MySQL 用户有复制权限
Canal 用户需要以下权限:
- `SELECT` - 查询表结构
- `REPLICATION SLAVE` - 读取 binlog
- `REPLICATION CLIENT` - 查看 binlog 状态
```sql
-- Canal 使用的用户需要有以下权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
```
#### 验证 MySQL 用户权限
由于部署服务器上没有安装 mysql 客户端,使用已部署的 MySQL 容器来测试连接:
```bash
# 通过 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 源服务器上执行授权。
#### 添加节点标签
```bash
docker node update --label-add review_canal=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
```
#### 拉取镜像
**方法一:直接从 Docker Hub 拉取(如网络可达)**
```bash
docker pull canal/canal-server:v1.1.5
```
**方法二:通过私有仓库中转(推荐)**
如果目标服务器无法访问 Docker Hub可通过私有仓库 harbor.sino-assist.com 中转:
```bash
# 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 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
docker service ls | grep review_canal
# 预期输出:
# review_canal_db replicated 1/1
# 查看详细状态
docker stack ps review_canal --no-trunc | grep -v Shutdown
```
#### 查看日志
```bash
docker service logs review_canal_db --tail 100
```
日志中应能看到:
- `destination:example start successful` 表示启动成功
- `position:xxx` 表示已连接 MySQL 并获取 binlog 位置
#### 验证 RabbitMQ 接收
登录 RabbitMQ 管理界面,检查:
1. Exchange `canal_exchange` 是否已创建
2. 是否有消息进入
### 14.6 连接信息
| 项目 | 值 |
|------|-----|
| MySQL 数据源 | 192.168.3.123:3306 |
| RabbitMQ | review-rabbitmq-stats:5672 |
| RabbitMQ Exchange | canal_exchange |
| RabbitMQ VHost | review |
### 14.7 常用运维命令
```bash
# 查看服务日志
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 用户名或密码错误,或用户没有复制权限
**解决:**
```bash
# 检查 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 文件不存在或已被清理
**解决:**
```bash
# 检查 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 地址错误或网络不通
**解决:**
```bash
# 确保 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
```yaml
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 常用运维命令
```bash
# 启动
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 需先修复官方源(已停止维护):
```bash
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](https://github.com/dongyubin/DockerHub)
```bash
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
```bash
docker login harbor.sino-assist.com
```
#### 3. 在源机器停服并同步数据
```bash
# 停止容器
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
```bash
# 修改 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. 启动并验证
```bash
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
**原因:** 订阅的表正则表达式不匹配
**解决:**
```bash
# 检查 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 的节点(建议选择内存较大的节点):
```bash
# 将 ES 部署到 ZD-BAK-APP2
docker node update --label-add review_es=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
```
#### 创建数据目录
在目标节点上创建数据目录并设置权限:
```bash
# 创建目录
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` 参数:
```bash
# 临时生效
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 拉取**
```bash
docker pull bitnami/elasticsearch:8.13.4
docker pull bitnami/kibana:8.13.4
```
**方法二:通过私有仓库中转(推荐)**
```bash
# 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 配置,创建配置文件:
```bash
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 部署节点上执行:
```bash
# 创建 plugins 目录
mkdir -p /mnt/data/volumes/elasticsearch-plugins
# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/elasticsearch-plugins
```
#### 下载并安装 IK 分词器
```bash
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 分词器安装
部署完成后,验证插件是否加载:
```bash
# 查看已安装的插件
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 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
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 或容器内测试。
```bash
# 方法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 运行节点
```bash
# 查看 ES 实际运行在哪个节点
docker service ps review_log_es_elasticsearch --format "table {{.Node}}\t{{.CurrentState}}"
```
#### 测试 Kibana 连通性
> **注意**:同 ES`localhost` 只能在 Kibana 实际运行的节点上使用。
```bash
# 方法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 常用运维命令
```bash
# 查看 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
```bash
# 查看所有索引
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` 参数默认值过低
**解决:**
```bash
# 临时生效
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 运行
**解决:**
```bash
# 修正目录权限
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
**解决:**
```bash
# 检查磁盘使用
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 无法访问
**解决:**
```bash
# 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 尚未启动完成,或网络不通
**解决:**
```bash
# 检查 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 端口被占用
**原因:** 端口被其他进程占用
**解决:**
```bash
# 检查端口占用
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
```bash
# 将所有索引的副本数设为 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 杀死
**原因:** 堆内存设置过大,超过系统可用内存
**解决:**
```bash
# 检查系统内存
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%
- 堆内存不超过 32GBJVM 压缩指针限制)
- 为操作系统和文件缓存预留足够内存
#### 15.9.9 无法拉取镜像
**问题:** `dial tcp xxx:443: i/o timeout``no such image`
**原因:** 无法访问 Docker Hub
**解决:** 参考 9.1 节「镜像拉取失败」,通过私有仓库中转
```bash
# 在能访问外网的服务器上
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 配置文件:
```bash
# 添加中文配置
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 部署前准备
#### 创建日志卷
```bash
# 创建外部卷用于收集应用日志
docker volume create review_logs
# 验证
docker volume ls | grep review_logs
```
> **重要**:此卷需要在所有节点上创建,用于应用服务挂载日志目录。
#### 拉取镜像
**方法一:直接从 Elastic 官方拉取**
```bash
# 在所有节点上执行(因为 Filebeat 是全局部署)
docker pull docker.elastic.co/logstash/logstash:8.13.4
docker pull docker.elastic.co/beats/filebeat:8.13.4
```
**方法二:通过私有仓库中转(推荐)**
```bash
# 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 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
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
```
#### 查看服务日志
```bash
# 查看 Logstash 日志
docker service logs review_log_logstash --tail 50
# 查看 Filebeat 日志
docker service logs review_log_filebeat --tail 50
```
#### 验证日志流转
```bash
# 检查 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 示例
```yaml
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 常用运维命令
```bash
# 查看 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 依赖:
```xml
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
</dependency>
```
**关联查询:**
1. 在 Kibana 中根据 `tid` 字段搜索特定请求的所有日志
2. 在 SkyWalking UI 中根据 Trace ID 查看完整调用链
3. 实现日志与链路追踪的关联分析
### 16.11 Kibana 日志查询
#### 访问地址
```
http://192.168.3.132:5601
```
#### 索引模式配置
1. 进入 **Management → Stack Management → Index Patterns**
2. 创建索引模式:`sslog-*`
3. 选择时间字段:`@timestamp`
#### 常用查询语法
```bash
# 查询某个服务的日志
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 卷未创建
**解决:**
```bash
# 创建卷
docker volume create review_logs
# 重启服务
docker service update --force review_log_filebeat
```
#### 16.10.2 Logstash 无法连接 Elasticsearch
**问题:** Logstash 日志显示无法连接 ES
**原因:** ES 服务未启动或网络不通
**解决:**
```bash
# 检查 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 规则
**解决:**
```bash
# 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 是全局部署,需要所有节点都有镜像
**解决:**
```bash
# 在缺少镜像的节点上拉取
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 规则不匹配
**解决:**
```bash
# 查看 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 磁盘空间不足
**问题:** 日志卷占用大量磁盘空间
**原因:** 日志文件未定期清理
**解决:**
```bash
# 检查卷使用情况
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 部署前准备
#### 添加节点标签
```bash
# 将 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 会产生大量索引数据,建议配置生命周期策略自动清理旧数据:
```bash
# 创建 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"
```
#### 拉取镜像
**方法一:直接拉取**
```bash
docker pull apache/skywalking-oap-server:10.0.0
docker pull apache/skywalking-ui:10.0.0
```
**方法二:通过私有仓库中转(推荐)**
```bash
# 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 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
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
```
#### 查看服务日志
```bash
# 查看 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 应用接入示例
1. 下载 SkyWalking Agent
```bash
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
```
2. 启动应用时添加 Agent 参数:
```bash
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 应用接入示例
```yaml
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 常用运维命令
```bash
# 查看 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 服务未启动或网络不通
**解决:**
```bash
# 检查 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 尚未启动完成
**解决:**
```bash
# 确认 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 内存设置过大或数据量过多
**解决:**
```bash
# 检查系统内存
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 索引数据增长过快
**原因:** 未配置索引生命周期策略
**解决:**
```bash
# 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
- 端口未开放
**解决:**
```bash
# 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 配置错误
- 时间范围选择不对
**解决:**
```bash
# 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 部署前准备
#### 添加节点标签
```bash
# 将 MongoDB 部署到 ZD-BAK-APP2
docker node update --label-add review_mongodb=1 ZD-BAK-APP2
# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'
```
#### 创建数据目录
```bash
# 在目标节点创建数据目录
mkdir -p /mnt/data/volumes/mongodb
# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/mongodb
```
#### 拉取镜像
**方法一:直接拉取**
```bash
docker pull bitnami/mongodb:6.0
```
**方法二:通过私有仓库中转(推荐)**
```bash
# 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 部署命令
```bash
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 验证部署
#### 查看服务状态
```bash
# 查看服务列表
docker service ls | grep review_mongodb
# 预期输出:
# review_mongodb_db replicated 1/1
# 查看详细状态
docker stack ps review_mongodb --no-trunc | grep -v Shutdown
```
#### 测试连接
```bash
# 方法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"
```
#### 验证挂载目录
```bash
# 查看容器挂载信息
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 常用运维命令
```bash
# 查看服务日志
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 常用命令
```bash
# 进入 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 运行
**解决:**
```bash
# 如果使用本地目录挂载,设置权限
chown -R 1001:1001 /path/to/mongodb/data
# 重启服务
docker service update --force review_mongodb_db
```
#### 18.9.2 无法连接 MongoDB
**问题:** 应用无法连接 MongoDB
**原因:**
- 网络不通
- 认证信息错误
- 端口未开放
**解决:**
```bash
# 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`
**原因:** 用户名或密码错误
**解决:**
```bash
# 确认连接字符串中的用户名和密码
# 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 写入失败,提示磁盘空间不足
**原因:** 数据卷所在磁盘空间不足
**解决:**
```bash
# 检查磁盘使用
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 验证生产主节点可访问
```bash
# 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 个节点上,需要在每个节点都拉取镜像。
**方法一:直接拉取(每个节点执行)**
```bash
docker pull bitnami/redis:7.0.11
docker pull bitnami/redis-sentinel:7.0.11
```
**方法二:通过私有仓库中转(推荐)**
```bash
# 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 部署命令
```bash
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 查看服务状态
```bash
# 查看服务列表
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` 容器,如果容器未运行可使用临时容器方式。
**方式一:使用已运行的容器(推荐)**
```bash
# 检查 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
```
**方式二:使用临时容器(备选,容器未运行时使用)**
```bash
# 定义 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 节点
```bash
# 在生产 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 测试数据同步
```bash
# 在从节点查询数据(只读)- 使用已运行容器
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` 脚本用于故障切换:
```bash
# 添加执行权限
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**
```bash
# 使用已运行容器
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**
```bash
# 检查 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**
```bash
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**
```bash
# 使用已运行容器
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修改应用配置**
```yaml
# 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`,该脚本会先将故障期间的数据同步到生产,确保数据不丢失。
**使用已运行容器(推荐):**
```bash
# 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
```
**备选:使用临时容器**
```bash
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 常用运维命令
**使用已运行容器(推荐):**
```bash
# 查看所有服务状态
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
```
**备选:使用临时容器(容器未运行时使用)**
```bash
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 节点纳入了选举范围
**解决方案:**
```yaml
# 使用 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 文件中的注释导致部署失败
**现象:**
```bash
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 会将运行时配置写入数据卷
- 重新部署时读取了旧的配置文件
**解决方案:**
```bash
# 删除 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 通用部署模式
```bash
cd /path/to/service
# 使用环境变量文件部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - <stack名称>
```
### 19.2 常用管理命令
```bash
# 查看所有 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 解析
```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` 中配置:
```groovy
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 中重复配置。
```groovy
// 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 版本或添加工具:
```bash
# 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 完整脚本(含详细注释)
```groovy
#!/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 参数 modulesOptionJSON 格式),转换为 Groovy 对象
// params.modulesOption 是 Jenkins 参数化构建时传入的 JSON 字符串
echo "jsonOption ${jsonOption}"
// 打印解析后的配置信息,用于调试
// ============================================================================
// 环境固定配置Review 环境)
// ============================================================================
jsonOption.harbor = "harbor.sino-assist.com"
// Harbor 私有镜像仓库地址
jsonOption.deploy_server = "192.168.3.132"
// 部署目标服务器 IPSwarm 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 格式示例
```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 | 远程执行密码 |
#### 脚本内容
```groovy
#!/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 部署前准备
#### 添加节点标签
前端服务通过节点标签约束部署位置,需要先在目标节点添加标签:
```bash
# 查看集群节点
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 创建配置目录
```bash
mkdir -p /data/nginx-proxy
```
#### 25.3.2 创建 Nginx 配置文件
```bash
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 容器
```bash
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个月
```bash
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
```
验证配置并手动执行一次:
```bash
# 验证配置语法
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/`,在该节点上执行以下配置。
```bash
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
```
验证配置并手动执行一次:
```bash
# 验证配置语法
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 会因权限问题跳过。
```bash
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
```
验证配置并手动执行一次:
```bash
# 验证配置语法
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 已写入,后续每天判断"今天已转过"而跳过。
排查命令:
```bash
# 查看 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` 失败,继续持有旧文件描述符。
解决:
```bash
# 修正新 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` 积压,需保留最近部分数据后重新切割。
```bash
# 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.132Swarm
**背景**:原 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 证书 include23368363 目录)
├── ssl.conf # sinoassist.com 证书 include2026 目录)
├── 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-gk18888/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 上创建目录并上传文件**
```bash
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 节点执行)**
```bash
docker stack deploy -c /data/nginx/docker-compose.yml nginx-review
```
验证:
```bash
docker service ls | grep nginx-review
docker service ps nginx-review_nginx-review
```
**3. 验证各域名转发正常**
```bash
# 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 上执行)**
```bash
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/` 属主为 rootlogrotate 配置中不需要 `su root sasys` 指令。
**5. DNS 切流**
联系 DNS 负责人,将所有域名 A 记录从 `192.168.3.110` 改为 `192.168.3.132`
建议分批切流:
1. Review 环境(`apireview``ccreview`
2. 内网工具(`jira``wiki``harbor``git` 等)
3. 生产核心(`crm1``api2``crm2``uat`
**6. 停止 3.132 旧的 nginx-proxy 容器**
DNS 切流并确认所有域名访问正常后执行:
```bash
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 日志停止增长:
```bash
# 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` 中的正则表达式:
```nginx
location ~ ^/(common|order|新路径1|新路径2)/ {
```
然后重启容器:
```bash
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 常见问题
#### 问题 1504 Gateway Timeout
后端响应超时,可在 location 块中添加:
```nginx
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
```
#### 问题 2WebSocket 连接失败
确保已配置 Upgrade 头:
```nginx
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.125ZD-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 上创建文件后热重载生效,无需重新部署:
```bash
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` 恢复。
**解决:**
1. 端口发布模式改为 `mode: host`,绕过 ingress 网络nginx 直接收到真实客户端 IP
2. 新增 `TZ=Asia/Shanghai` 环境变量修正时区
`docker-compose.yml` 关键变更:
```yaml
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` 不显示端口映射,属正常现象,实际端口仍正常监听。
重新部署生效(会有秒级中断):
```bash
cd /opt/swarm/support/nginx-review-132
docker stack deploy --compose-file docker-compose.yml nginx-review
```
#### 25.9.3 修复 crm1/crm2 切换后域名访问 4042026-04-29
**问题:** DNS 从 3.110 切到 3.132 后,`crm1.sino-assist.com` 访问返回 Whitelabel 404crm2 同理。
**根因:** `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。
**排查过程:**
```bash
# 3.110 走域名返回 200 html3.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`
```nginx
location / {
proxy_pass http://192.168.1.209:8080/;
proxy_set_header Host $host; # 补充此行
proxy_set_header X-Forwarded-Host $server_name;
...
}
```
**生效方式(无中断):**
```bash
# 将修改后的 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 域名访问 4042026-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`
**生效方式(无中断):**
```bash
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`
```nginx
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`)。
**生效方式(有秒级中断):**
```bash
cd /opt/swarm/support/nginx-review-132
docker stack deploy --compose-file docker-compose.yml nginx-review
```
**验证:**
```bash
# 验证 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 切换后 404location 块补全 proxy_set_header Host $host |
| 2026-05-07 | 修复 uat 域名 404uat.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检查状态
```bash
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执行完整切换
```bash
./failover-to-review.sh full
```
脚本会自动执行:
1. **提升 132 为 Master**
```redis
SLAVEOF NO ONE
```
2. **配置 133/134 指向 132**
```redis
SLAVEOF 192.168.3.132 6379
```
3. **部署 Review Sentinel**
- 监控名称:`reviewmaster`
- Sentinel 端口26379
#### 步骤 3修改应用配置
切换应用连接到 Review Sentinel
```yaml
# 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
```
或直连模式:
```yaml
spring:
redis:
host: 192.168.3.132
port: 6379
password: sino#650
```
### 27.5 安全回滚流程
当生产 Redis 恢复后,需要将数据同步回生产并恢复原有架构。
#### 回滚前提条件
- ✅ 132 当前是 Master处于故障切换状态
- ✅ 生产 10.56 已恢复并可连接
- ✅ **所有应用已切换到 Review Sentinel**
- ✅ **生产 Sentinel 已停止**
#### 步骤 1停止生产 Sentinel手动
**重要**:必须先在生产服务器上执行!
```bash
# SSH 到生产服务器
ssh root@192.168.10.56
# 停止生产 Sentinel
docker stack rm prod_sentinel
```
**原因**
- 回滚步骤会让 10.56 临时变成 132 的从节点
- 如果 Sentinel 还在运行,会检测到 master 下线并触发故障转移
- 可能导致脑裂和数据不一致
#### 步骤 2执行安全回滚
```bash
./failover-to-review.sh rollback
```
脚本会执行以下步骤:
**2.1 配置 10.56 作为 132 的从节点(反向同步)**
```redis
# 在 10.56 执行
SLAVEOF 192.168.3.132 6379
```
**2.2 等待数据同步完成**
- 监控 `master_repl_offset` 和 `slave_repl_offset`
- 偏移量差异 < 100 字节时自动继续
- 建议此时停止 Review 应用对 Redis 的写入
**2.3 切换主从关系**
```redis
# 将 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
```bash
# 在生产服务器执行
docker stack deploy -c sentinel-compose.yml prod_sentinel
```
#### 步骤 4修改应用配置
将应用连接切换回生产 Sentinel。
### 27.6 数据一致性保证
| 阶段 | 措施 | 说明 |
|------|------|------|
| 回滚前 | 反向同步 | 10.56 先作为 132 的从节点,同步故障期间的数据 |
| 回滚中 | 132 只读 | `min-replicas-to-write=99` 防止切换期间写入 |
| 回滚后 | 偏移量验证 | 确认数据完全同步 |
### 27.7 不安全回滚(不推荐)
如果紧急情况需要快速恢复,可以使用不安全回滚:
```bash
./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
- [ ] 验证应用功能正常