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

197 KiB
Raw Blame History

Docker Swarm 部署手册Review 环境)


一、部署概览

环境信息

项目
主节点 ZD-BAK-APP2 (192.168.x.132)
操作系统 CentOS 7
网络名称 review
网络子网 10.18.0.0/16

部署进度

阶段 状态
系统准备 完成
数据盘挂载 完成
Docker 安装 完成
Swarm 初始化 完成
Overlay 网络 完成
Portainer 完成
MySQL 主从复制 (mysql-repl-tool) 完成
RabbitMQ 集群 完成
Nacos 集群 完成
XXL-Job-Admin 完成
Canal 完成
Elasticsearch + Kibana 完成
Log (Logstash + Filebeat) 完成
SkyWalking 完成
MongoDB 完成
Redis Sentinel 集群 完成
后端服务 (sa-server) 完成
前端服务 (sa-cc) 完成
Nginx 反向代理 完成

数据盘信息

项目
设备 /dev/sdb
容量 3.7T
文件系统 ext4
UUID 988dd535-6531-4a3e-89b7-104de26adcf2
挂载点 /mnt/data
数据目录 /mnt/data/volumes

集群节点信息

节点 主机名 IP 角色
节点1 ZD-BAK-APP1 192.168.3.134 Worker
节点2 ZD-BAK-APP2 192.168.3.132 Manager (Leader)
节点3 zd-bak-app3 192.168.3.133 Worker
节点4 ZD-FDFS4 192.168.3.125 Worker

二、系统准备

2.1 更换阿里云 YUM 源

# 备份官方源
cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak

# 下载阿里云源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

# 重建缓存
yum clean all && yum makecache

2.2 验证 YUM 源

grep aliyun /etc/yum.repos.d/CentOS-Base.repo

2.3 数据盘挂载配置

重要:在部署服务前,必须先完成数据盘挂载。如果先部署服务再挂载数据盘,已写入的数据将被"遮盖",需要迁移处理。

2.3.1 挂载原理

┌─────────────────────────────────────────────────────────────────┐
│                        Linux 文件系统树                          │
│                              /                                   │
│                              │                                   │
│         ┌────────────────────┼────────────────────┐              │
│         │                    │                    │              │
│       /home                /mnt                 /var             │
│    (sda3 64G)                │                                   │
│                            /data ◄── /dev/sdb 3.7T 数据盘        │
│                              │                                   │
│                           /volumes                               │
│                              │                                   │
│              ┌───────────────┼───────────────┐                   │
│              │               │               │                   │
│         /elasticsearch   /mongodb        /kibana                 │
└─────────────────────────────────────────────────────────────────┘

说明:

  • /dev/sdb 是 3.7T 数据盘,挂载到 /mnt/data
  • 所有写入 /mnt/data 及其子目录的数据都存储在数据盘上
  • 服务数据目录统一放在 /mnt/data/volumes/

2.3.2 查看磁盘信息

# 查看所有磁盘和分区
lsblk

# 预期输出:
# NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
# sda      8:0    0 894.3G  0 disk
# ├─sda1   8:1    0   200M  0 part /boot/efi
# ├─sda2   8:2    0     1G  0 part /boot
# ├─sda3   8:3    0    64G  0 part /home
# ├─sda4   8:4    0  31.3G  0 part [SWAP]
# └─sda5   8:5    0   797G  0 part /
# sdb      8:16   0   3.7T  0 disk          ← 数据盘(待挂载或已挂载)

# 查看磁盘文件系统类型和 UUID
blkid /dev/sdb

2.3.3 挂载数据盘

# 创建挂载点
mkdir -p /mnt/data

# 如果数据盘未格式化,先格式化(慎重!会清除数据)
# mkfs.ext4 /dev/sdb

# 临时挂载(重启后失效)
mount /dev/sdb /mnt/data

# 验证挂载
df
 -h /mnt/data

2.3.4 配置开机自动挂载

# 获取磁盘 UUID
blkid /dev/sdb
# 输出示例:/dev/sdb: UUID="988dd535-6531-4a3e-89b7-104de26adcf2" TYPE="ext4"

# 备份 fstab
cp /etc/fstab /etc/fstab.bak

# 添加自动挂载配置(使用 UUID 更可靠)
echo 'UUID=988dd535-6531-4a3e-89b7-104de26adcf2 /mnt/data ext4 defaults 0 0' >> /etc/fstab

# 验证 fstab 配置
cat /etc/fstab

# 测试配置是否正确(不应报错)
mount -a

# 确认挂载成功
df -h /mnt/data

fstab 格式说明:

字段 说明 示例值
设备 UUID 或设备路径 UUID=988dd535-...
挂载点 目录路径 /mnt/data
文件系统 ext4/xfs/等 ext4
选项 挂载选项 defaults
dump 备份标志 0
fsck 检查顺序 0

2.3.5 创建服务数据目录

# 创建各服务数据目录
mkdir -p /mnt/data/volumes/elasticsearch
mkdir -p /mnt/data/volumes/kibana/data
mkdir -p /mnt/data/volumes/kibana/config
mkdir -p /mnt/data/volumes/mongodb

# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana
chown -R 1001:1001 /mnt/data/volumes/mongodb

# 验证
ls -la /mnt/data/volumes/

2.3.6 常见问题

问题 1fstab 语法错误

错误示例:

"/dev/sdb /mnt/data ext4 defaults 0 0"   ← 整行被引号包裹(错误)

正确写法:

UUID=988dd535-6531-4a3e-89b7-104de26adcf2 /mnt/data ext4 defaults 0 0

问题 2先部署服务后挂载数据盘

如果在挂载数据盘之前部署了服务,数据会写入根分区。挂载数据盘后,原数据会被"遮盖"。

解决方案:

# 1. 停止相关服务
docker stack rm review_log_es
docker stack rm review_mongodb

# 2. 卸载数据盘
umount /mnt/data

# 3. 查看原数据(此时可以看到写在根分区的数据)
ls -la /mnt/data/volumes/
du -sh /mnt/data/volumes/*

# 4. 备份原数据
mv /mnt/data /mnt/data_old

# 5. 重新创建挂载点并挂载数据盘
mkdir -p /mnt/data
mount /dev/sdb /mnt/data

# 6. 迁移数据到数据盘
cp -a /mnt/data_old/volumes /mnt/data/

# 7. 设置权限
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana
chown -R 1001:1001 /mnt/data/volumes/mongodb

# 8. 重新部署服务
cd /opt/swarm/support/elasticsearch
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es

cd /opt/swarm/support/mongodb
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb

# 9. 确认无误后删除备份
rm -rf /mnt/data_old

三、Docker 安装

3.1 卸载旧版本

sudo yum remove -y docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine

3.2 安装依赖工具

sudo yum install -y yum-utils

3.3 配置 Docker 仓库

# 使用阿里云镜像源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

3.4 安装 Docker

sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

3.5 配置 Docker Daemon

mkdir -p /etc/docker

echo '{"log-opts":{"max-size":"1g","max-file":"3"},"registry-mirrors":["https://registry.cn-hangzhou.aliyuncs.com"]}' > /etc/docker/daemon.json

配置说明:

配置项 说明
log-opts.max-size 1g 单个日志文件最大 1GB
log-opts.max-file 3 最多保留 3 个日志文件
registry-mirrors 阿里云 镜像加速器

3.6 启动 Docker

sudo systemctl daemon-reload
sudo systemctl start docker
sudo systemctl enable docker

# 验证
systemctl status docker
docker version

3.7 关闭防火墙

systemctl stop firewalld
systemctl disable firewalld

3.8 调整文件描述符限制

# 临时生效
ulimit -SHn 65536

# 永久生效
echo '* soft nofile 65535' >> /etc/security/limits.conf
echo '* hard nofile 65535' >> /etc/security/limits.conf

# 验证
tail -2 /etc/security/limits.conf

四、Docker Swarm 初始化

4.1 初始化集群(主节点)

docker swarm init

4.2 验证节点

docker node ls

预期输出:

ID            HOSTNAME      STATUS    AVAILABILITY   MANAGER STATUS
xxx *         ZD-BAK-APP2   Ready     Active         Leader

五、创建 Overlay 网络

docker network create \
  --driver=overlay \
  --subnet=10.18.0.0/16 \
  --scope swarm \
  --attachable \
  review

验证:

docker network ls | grep review

六、部署 Portainer

6.1 配置文件

docker-compose.yml:

version: '3.2'

services:
  agent:
    image: portainer/agent:2.20.3
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - agent_network
    deploy:
      mode: global
      placement:
        constraints: [node.platform.os == linux]

  portainer:
    image: portainer/portainer-ce:2.20.3
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    ports:
      - "9443:9443"
      - "9000:9000"
      - "8000:8000"
    volumes:
      - portainer_data:/data
    networks:
      - agent_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.hostname == ZD-BAK-APP2]

networks:
  agent_network:
    driver: overlay
    attachable: true

volumes:
  portainer_data:

6.2 部署命令

cd /path/to/docker-swarm-review/portainer
docker stack deploy --compose-file docker-compose.yml portainer

6.3 验证

docker stack ps portainer
docker service ls | grep portainer

6.4 访问地址

  • HTTPS: https://<服务器IP>:9443
  • HTTP: http://<服务器IP>:9000

七、多服务器节点管理

7.1 节点角色说明

角色 说明
Manager 管理节点,负责集群调度和状态管理
Worker 工作节点,只运行容器

建议3 节点集群至少 1 个 Manager生产环境建议 3 个 Manager。

7.2 新节点准备工作

在新服务器上依次执行(同主节点):

  1. 更换阿里云 YUM 源
  2. 安装 Docker
  3. 配置 Docker Daemon
  4. 启动 Docker
  5. 关闭防火墙
  6. 调整文件描述符限制

7.3 获取加入 Token主节点执行

# 获取 Worker 加入 Token
docker swarm join-token worker

# 获取 Manager 加入 Token
docker swarm join-token manager

7.4 加入集群(新节点执行)

docker swarm join --token SWMTKN-1-xxxxx <主节点IP>:2377

7.5 验证节点(主节点执行)

docker node ls

预期输出:

ID            HOSTNAME      STATUS    AVAILABILITY   MANAGER STATUS
xxx *         ZD-BAK-APP2   Ready     Active         Leader
xxx           节点2          Ready     Active
xxx           节点3          Ready     Active

7.6 节点标签管理

# 添加标签
docker node update --label-add <标签名>=<值> <节点名>

# 示例:标记 MySQL 运行节点
docker node update --label-add review_mysql=1 ZD-BAK-APP2

# 查看节点标签
docker node inspect <节点名> --format '{{.Spec.Labels}}'

7.7 节点管理命令

# 查看所有节点
docker node ls

# 将节点设为 Drain停止接收新任务
docker node update --availability drain <节点名>

# 将节点恢复为 Active
docker node update --availability active <节点名>

# 移除节点(在主节点执行)
docker node rm <节点名>

# 离开集群(在该节点执行)
docker swarm leave
docker swarm leave --force  # Manager 节点需要 --force

八、Docker Config 管理

8.1 什么是 Docker Config

Docker Config 用于在 Swarm 集群中统一管理配置文件,无需在每个节点放置文件。

8.2 创建 Config

# 从文件创建
docker config create <config名称> <文件路径>

# 示例
docker config create review_mysql_conf_v1 /tmp/mysql_custom.cnf

8.3 查看 Config

docker config ls
docker config inspect <config名称>

8.4 在 docker-compose.yml 中引用

services:
  myservice:
    configs:
      - source: my_config
        target: /path/in/container/config.conf

configs:
  my_config:
    external: true
    name: my_config_name

8.5 删除 Config

docker config rm <config名称>

注意:删除前需确保没有服务在使用该 Config。


九、Docker 挂载详解

9.1 Docker 挂载类型

┌─────────────────────────────────────────────────────────────────┐
│                      Docker 挂载类型                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Bind Mount绑定挂载← 本环境使用的方式                      │
│     ┌──────────────┐         ┌──────────────┐                   │
│     │   宿主机      │         │    容器      │                   │
│     │ /mnt/data/   │ ═════►  │ /bitnami/    │                   │
│     │   volumes/   │         │   mongodb    │                   │
│     │   mongodb    │         │              │                   │
│     └──────────────┘         └──────────────┘                   │
│     - 直接映射宿主机目录到容器                                    │
│     - 路径由用户指定                                             │
│     - 适合需要在特定位置存储数据                                  │
│                                                                  │
│  2. VolumeDocker 管理卷)                                      │
│     ┌──────────────┐         ┌──────────────┐                   │
│     │ Docker 管理   │         │    容器      │                   │
│     │ /var/lib/    │ ═════►  │ /bitnami/    │                   │
│     │ docker/      │         │   mongodb    │                   │
│     │ volumes/xxx  │         │              │                   │
│     └──────────────┘         └──────────────┘                   │
│     - Docker 自动管理存储位置                                    │
│     - 默认在 /var/lib/docker/volumes/                           │
│     - 便于 Docker 管理,但位置不可控                             │
│                                                                  │
│  3. tmpfs内存挂载                                            │
│     - 数据存储在内存中                                           │
│     - 容器停止后数据丢失                                         │
│     - 用于临时敏感数据                                           │
└─────────────────────────────────────────────────────────────────┘

9.2 docker-compose.yml 中的挂载配置

Bind Mount 方式(本环境使用):

volumes:
  - '/mnt/data/volumes/mongodb:/bitnami/mongodb'
#    ↑ 宿主机路径              ↑ 容器内路径

Volume 方式Docker 管理):

services:
  db:
    volumes:
      - 'mongodb_data:/bitnami/mongodb'

volumes:
  mongodb_data:
    driver: local

9.3 验证挂载是否生效

方法 1检查容器挂载信息

# 检查 ES 容器挂载
docker inspect $(docker ps -q -f name=review_log_es_elasticsearch) --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

# 检查 MongoDB 容器挂载
docker inspect $(docker ps -q -f name=review_mongodb_db) --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

# 预期输出:
# bind: /mnt/data/volumes/elasticsearch -> /bitnami/elasticsearch/data
# bind: /mnt/data/volumes/mongodb -> /bitnami/mongodb

方法 2确认数据写入位置

# 查看宿主机目录是否有数据生成
ls -la /mnt/data/volumes/elasticsearch/
ls -la /mnt/data/volumes/mongodb/

# 查看数据大小
du -sh /mnt/data/volumes/*

方法 3确认数据在数据盘上

# 确认路径属于 /dev/sdb3.7T 数据盘)
df -h /mnt/data/volumes/elasticsearch
df -h /mnt/data/volumes/mongodb

# 预期输出应显示 /dev/sdb 和 3.7T 容量

方法 4写入测试文件验证

# 在宿主机创建测试文件
echo "test from host" > /mnt/data/volumes/mongodb/test_host.txt

# 进入容器查看是否存在
docker exec $(docker ps -q -f name=review_mongodb_db) cat /bitnami/mongodb/test_host.txt

# 在容器内创建测试文件
docker exec $(docker ps -q -f name=review_mongodb_db) sh -c 'echo "test from container" > /bitnami/mongodb/test_container.txt'

# 在宿主机查看
cat /mnt/data/volumes/mongodb/test_container.txt

# 清理测试文件
rm /mnt/data/volumes/mongodb/test_*.txt

如果双向都能看到文件,说明挂载完全正确。


十、常见问题及解决

10.1 镜像拉取失败

问题: dial tcp xxx:443: i/o timeout

解决方案:

方案 1更换镜像加速器

echo '{"log-opts":{"max-size":"1g","max-file":"3"},"registry-mirrors":["https://registry.cn-hangzhou.aliyuncs.com"]}' > /etc/docker/daemon.json
systemctl daemon-reload
systemctl restart docker

方案 2使用其他源拉取后打标签

docker pull <其他源>/<镜像名>
docker tag <其他源>/<镜像名> <原镜像名>

方案 3从其他机器导入镜像

# 在能访问的机器上导出
docker save <镜像名> -o image.tar

# 传输到目标机器后导入
docker load -i image.tar

方案 4通过私有仓库中转推荐

如果有私有镜像仓库(如 Harbor可以先在能访问外网的服务器拉取镜像推送到私有仓库再从私有仓库拉取

# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上操作
docker pull <原镜像名>:<版本>
docker tag <原镜像名>:<版本> harbor.sino-assist.com/library/<镜像名>:<版本>
docker login harbor.sino-assist.com
docker push harbor.sino-assist.com/library/<镜像名>:<版本>

# 2. 在目标服务器(如 132 服务器)上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/library/<镜像名>:<版本>
docker tag harbor.sino-assist.com/library/<镜像名>:<版本> <原镜像名>:<版本>

示例Canal 镜像):

# 50 服务器上
docker pull canal/canal-server:v1.1.5
docker tag canal/canal-server:v1.1.5 harbor.sino-assist.com/library/canal-server:v1.1.5
docker push harbor.sino-assist.com/library/canal-server:v1.1.5

# 132 服务器上
docker pull harbor.sino-assist.com/library/canal-server:v1.1.5
docker tag harbor.sino-assist.com/library/canal-server:v1.1.5 canal/canal-server:v1.1.5

10.2 镜像标签打错处理

问题: 给镜像打标签时打错了(如版本号或仓库名写错)

解决方案:

删除本地错误标签:

# 删除错误的本地标签(只删除标签,不删除镜像本身)
docker rmi <错误的标签名>

# 示例:删除打错的标签
docker rmi harbor.sino-assist.com/bitnami/redis:wrong-version

# 然后重新打正确的标签
docker tag <原镜像名>:<版本> <正确的标签名>

删除已推送到 Harbor 的错误标签:

# 方法1通过 Harbor Web 界面删除
# 登录 Harbor -> 项目 -> 仓库 -> 选择镜像 -> 删除标签

# 方法2通过 API 删除(需要管理员权限)
# 1. 先获取镜像的 digest
curl -u <用户名>:<密码> -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  https://harbor.sino-assist.com/v2/<项目>/<仓库>/manifests/<标签> -I | grep Docker-Content-Digest

# 2. 使用 digest 删除
curl -X DELETE -u <用户名>:<密码> \
  https://harbor.sino-assist.com/v2/<项目>/<仓库>/manifests/<digest>

示例:

# 场景:不小心把 redis:7.0.11 标签打成了 redis:7.0.1
docker rmi harbor.sino-assist.com/bitnami/redis:7.0.1

# 重新打正确标签
docker tag bitnami/redis:7.0.11 harbor.sino-assist.com/bitnami/redis:7.0.11
docker push harbor.sino-assist.com/bitnami/redis:7.0.11

注意: docker rmi 删除的是标签引用。如果镜像只有这一个标签,镜像本身也会被删除。如果镜像有多个标签引用,只会删除指定的标签。

10.3 Portainer 超时锁定

问题: Your Portainer instance timed out for security purposes

解决:

docker service update --force portainer_portainer

然后立即访问并设置管理员密码5 分钟内)。

10.4 iptables 规则错误

问题: iptables: No chain/target/match by that name

解决:

systemctl restart docker

10.5 EOF Heredoc 语法问题

问题: 多行内容写入文件时出现 > 等待符号

原因:

  • 结束的 EOF 前面有空格或缩进
  • Windows 与 Linux 换行符不一致

解决: 使用单行 echo 或 vim 手动创建:

echo '内容' > /path/to/file

或:

vim /path/to/file
# 粘贴内容后 :wq 保存

10.6 Windows 换行符问题

问题: yaml: line xx: could not find expected ':'

解决:

sed -i 's/\r$//' /path/to/file

10.7 服务状态为 Ready 不变为 Running

问题: docker stack ps 显示服务状态一直是 ReadyPreparing

排查:

docker stack ps <stack名称> --no-trunc
docker service logs <服务名>

常见原因:

  • 镜像拉取失败
  • 节点标签不匹配
  • 资源不足

10.8 Swarm 跨节点服务报 "No such image"

问题: docker stack ps 显示 Rejected: No such image

原因: Swarm 跨节点部署时,目标节点上没有所需镜像,且无法从镜像仓库拉取。

解决: 在目标节点上手动拉取镜像:

# 方法1直接拉取
docker pull <镜像名>

# 方法2使用国内镜像源
docker pull dockerproxy.cn/<镜像名>
docker tag dockerproxy.cn/<镜像名> <原镜像名>

# 方法3从其他机器导入
docker save <镜像名> -o image.tar
# 传输到目标节点后
docker load -i image.tar

10.9 MySQL Slave 不断重启

问题: MySQL Slave 服务启动后约 90 秒被关闭,状态显示 Complete 后重新启动。

原因: Bitnami MySQL 的 healthcheck 脚本会检查复制状态如果复制未建立或有问题healthcheck 失败导致容器重启。

排查:

# 查看日志确认是否被 SHUTDOWN 信号终止
docker service logs <slave服务名> 2>&1 | grep -i "shutdown"

# 在 Slave 运行期间检查复制状态
docker exec $(docker ps -q -f name=mysql-slave) mysql -uroot -p'<密码>' -e "SHOW SLAVE STATUS\G"

解决方案:

方案 1调整 healthcheck 参数(推荐)

增加 start_period 给足够的初始化时间:

healthcheck:
  test: ['CMD', '/opt/bitnami/scripts/mysql/healthcheck.sh']
  interval: 30s
  timeout: 10s
  retries: 5
  start_period: 180s

方案 2删除 Slave 数据卷重新初始化

如果 Slave 数据卷有旧数据,会跳过复制配置初始化:

docker stack rm <stack名称>
sleep 15
docker volume rm <slave数据卷名称>
# 重新部署

10.10 Swarm 节点间通信问题

问题: 跨节点的服务无法通过 overlay 网络通信

排查:

# 检查节点状态
docker node ls

# 测试节点间基础连通性
ping <目标节点IP>

# 测试 Swarm 通信端口 (7946)
timeout 3 bash -c "echo >/dev/tcp/<目标节点IP>/7946" && echo "OK" || echo "FAILED"

# 测试 overlay 网络内服务连通性
docker run --rm --network <网络名> <镜像> <测试命令>

解决:

  1. 确保所有节点防火墙已关闭:
systemctl stop firewalld
systemctl disable firewalld
  1. 或者开放 Swarm 所需端口:
firewall-cmd --permanent --add-port=2377/tcp  # 集群管理(仅 Manager
firewall-cmd --permanent --add-port=7946/tcp  # 节点间通信
firewall-cmd --permanent --add-port=7946/udp
firewall-cmd --permanent --add-port=4789/udp  # Overlay 网络
firewall-cmd --reload

10.11 复制状态为空SHOW SLAVE STATUS 无输出)

问题: 进入 Slave 容器执行 SHOW SLAVE STATUS 没有任何输出

原因: Slave 初始化时没有配置复制,可能是:

  • 数据卷已存在旧数据,跳过了初始化
  • 环境变量未正确传递
  • 初始化时无法连接 Master

排查:

# 检查环境变量是否正确
docker service inspect <slave服务名> --pretty | grep -A 30 "Env"

# 查看初始化日志是否有复制配置
docker service logs <slave服务名> 2>&1 | grep -iE "repl|slave|master|configur"

解决:

# 删除 stack 和 slave 数据卷
docker stack rm <stack名称>
sleep 15
docker volume rm <slave数据卷名称>

# 确保 Master 已启动并可连接
docker run --rm --network <网络名> <mysql镜像> mysql -h <master服务名> -uroot -p'<密码>' -e "SELECT 1"

# 重新部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - <stack名称>

10.12 镜像名称拼写错误

问题: 部署时提示镜像拉取失败,但镜像名看起来正确

原因: 镜像名称可能有拼写错误,如 rabbitmg 应为 rabbitmq

排查:

# 检查 docker-compose.yml 中的镜像名
grep -i "image:" docker-compose.yml

解决: 修正镜像名称后重新部署

10.13 RabbitMQ 集群节点无法加入

问题: RabbitMQ 队列节点无法加入 stats 节点组成集群

原因:

  • ERL_COOKIE 不一致
  • 节点间网络不通
  • stats 节点尚未启动完成

排查:

# 检查集群状态
docker exec $(docker ps -q -f name=rabbitmq_stats) rabbitmqctl cluster_status

# 检查节点日志
docker service logs <队列服务名> --tail 100

解决:

# 确保 ERL_COOKIE 一致(所有节点必须相同)
# 检查 docker-compose.yml 中 RABBITMQ_ERL_COOKIE 配置

# 删除数据卷重新初始化
docker stack rm <stack名称>
sleep 15
docker volume ls | grep rabbitmq | awk '{print $2}' | xargs docker volume rm
# 重新部署

10.14 RabbitMQ 网络分区 (Network Partition)

问题: rabbitmqctl cluster_status 显示 Network Partitions 不为空

原因: 节点间网络中断后恢复,导致集群分裂

解决:

# 方法1重启受影响的节点
docker service update --force <受影响的服务名>

# 方法2手动解决分区在 stats 节点执行)
docker exec $(docker ps -q -f name=rabbitmq_stats) rabbitmqctl forget_cluster_node <分区节点名>
docker service update --force <分区节点服务名>

10.15 RabbitMQ 无法登录管理界面

问题: 部署成功但无法使用配置的用户名密码登录管理界面

原因:

  • 用户不存在
  • 密码不正确
  • 用户没有管理员权限

排查:

# 查看用户列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users

解决:

# 如果用户不存在,创建用户
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl add_user root gkxl650

# 设置管理员权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_user_tags root administrator

# 设置 vhost 权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p /review root ".*" ".*" ".*"

# 如果用户存在但密码不对,重置密码
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl change_password root <新密码>

十一、部署 MySQL 主从复制 (mysql-repl-tool)

10.1 服务说明

用于 Nacos、XXL-Job 等工具类服务的专用 MySQL 数据库,采用主从复制架构。

项目
Stack 名称 review_tool_mysql
镜像 bitnami/mysql:8.0
Master 节点 ZD-BAK-APP1
Master 端口 25306
Slave 节点 ZD-BAK-APP2
Slave 端口 25307

10.2 部署前准备

创建 MySQL 配置文件

# Master 配置
cat > /tmp/mysql_master_custom.cnf << 'EOF'
[mysqld]
max_connections=500
max_allowed_packet=64M
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
log-bin=mysql-bin
binlog-format=ROW
server_id=1
EOF

# Slave 配置
cat > /tmp/mysql_slave_custom.cnf << 'EOF'
[mysqld]
max_connections=500
max_allowed_packet=64M
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
log-bin=mysql-bin
binlog-format=ROW
server_id=2
read_only=1
EOF

创建 Docker Config

docker config create review_tool_mysql_master_conf_v1 /tmp/mysql_master_custom.cnf
docker config create review_tool_mysql_slave_conf_v1 /tmp/mysql_slave_custom.cnf

# 验证
docker config ls | grep review_tool

确保所有节点都有镜像

重要Swarm 跨节点部署时,每个节点都需要有镜像。

# 在每个节点上拉取镜像
docker pull bitnami/mysql:8.0

# 如果官方源拉取失败,使用国内镜像源
docker pull dockerproxy.cn/bitnami/mysql:8.0
docker tag dockerproxy.cn/bitnami/mysql:8.0 bitnami/mysql:8.0

确保所有节点防火墙已关闭

# 在每个节点执行
systemctl stop firewalld
systemctl disable firewalld

10.3 部署命令

cd /path/to/docker-swarm-review/mysql-repl-tool

# 转换 Windows 换行符(如果文件从 Windows 传输)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_tool_mysql

10.4 验证部署

# 查看服务状态
docker service ls | grep review_tool_mysql

# 预期输出:
# review_tool_mysql_mysql-master   replicated   1/1
# review_tool_mysql_mysql-slave    replicated   1/1

# 查看详细状态
docker stack ps review_tool_mysql

# 验证复制状态
docker exec $(docker ps -q -f name=mysql-slave) mysql -uroot -p'gkxl2024#@' -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO|Slave_SQL|Seconds_Behind"

# 预期输出:
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes
# Seconds_Behind_Master: 0

10.5 连接信息

项目 Master Slave
主机 192.168.3.134 ZD-BAK-APP2 IP
端口 25306 25307
Root 密码 gkxl2024#@ gkxl2024#@
普通用户 zd_tool zd_tool
普通密码 gkxl2024#@ gkxl2024#@

10.6 常用运维命令

# 查看服务日志
docker service logs review_tool_mysql_mysql-master --tail 50
docker service logs review_tool_mysql_mysql-slave --tail 50

# 进入 Master 容器
docker exec -it $(docker ps -q -f name=mysql-master) bash

# 进入 Slave 容器
docker exec -it $(docker ps -q -f name=mysql-slave) bash

# 强制重启服务
docker service update --force review_tool_mysql_mysql-slave

# 删除并重新部署
docker stack rm review_tool_mysql
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_tool_mysql

十二、部署 RabbitMQ 集群

11.1 服务说明

RabbitMQ 集群采用 3 节点架构,包含 1 个管理节点 (stats) 和 2 个队列节点 (queue1, queue2)。

项目
Stack 名称 review_rabbitmq
镜像 harbor.sino-assist.com/bitnami/rabbitmq:3.11
管理界面端口 15672
AMQP 端口 5672
STOMP 端口 61613
WebSocket 端口 15674

11.2 节点分布

服务 类型 节点标签 部署节点
stats 管理节点 rabbit_stats==1 ZD-BAK-APP2
queue1 队列节点 (disc) rabbit_queue1==1 ZD-BAK-APP1
queue2 队列节点 (disc) rabbit_queue2==1 zd-bak-app3

11.3 部署前准备

添加节点标签

# stats 部署到 ZD-BAK-APP2
docker node update --label-add rabbit_stats=1 ZD-BAK-APP2

# queue1 部署到 ZD-BAK-APP1
docker node update --label-add rabbit_queue1=1 ZD-BAK-APP1

# queue2 部署到 zd-bak-app3
docker node update --label-add rabbit_queue2=1 zd-bak-app3

# 验证标签
docker node ls -q | xargs -I {} docker node inspect {} --format '{{.Description.Hostname}}: {{.Spec.Labels}}'

确保所有节点有镜像

# 在每个目标节点上执行
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/rabbitmq:3.11

11.4 部署命令

cd /path/to/docker-swarm-review/rabbitmq

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose-review.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose-review.yml | docker stack deploy --compose-file - review_rabbitmq

11.5 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_rabbitmq

# 预期输出:
# review_rabbitmq_stats    replicated   1/1
# review_rabbitmq_queue1   replicated   1/1
# review_rabbitmq_queue2   replicated   1/1

# 查看详细状态
docker stack ps review_rabbitmq --no-trunc | grep -v Shutdown

检查集群状态

# 进入 stats 节点检查集群状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl cluster_status

预期输出应包含:

  • Running Nodes: rabbit@stats, rabbit@queue1, rabbit@queue2
  • Alarms: (none)
  • Network Partitions: (none)

检查节点健康状态

docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl node_health_check

查看集群中的所有节点

docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_nodes

11.6 连接信息

项目
管理界面 http://<ZD-BAK-APP2_IP>:15672
用户名 root
密码 gkxl650
AMQP 连接 amqp://root:gkxl650@<服务器IP>:5672/review
Virtual Host /review

11.7 用户管理

查看用户列表

docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_users

如果无法登录,重置密码

# 修改用户密码
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl change_password root <新密码>

如果用户不存在,创建用户

# 创建用户
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl add_user root gkxl650

# 设置管理员权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_user_tags root administrator

# 设置 vhost 权限
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl set_permissions -p /review root ".*" ".*" ".*"

删除用户

docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl delete_user <用户名>

11.8 常用运维命令

# 查看服务日志
docker service logs review_rabbitmq_stats --tail 50
docker service logs review_rabbitmq_queue1 --tail 50
docker service logs review_rabbitmq_queue2 --tail 50

# 进入 stats 容器
docker exec -it $(docker ps -q -f name=review_rabbitmq_stats) bash

# 查看集群状态
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl cluster_status

# 查看队列列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_queues

# 查看连接列表
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_connections

# 删除并重新部署
docker stack rm review_rabbitmq
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose-review.yml | docker stack deploy --compose-file - review_rabbitmq

十三、部署 Nacos 集群

12.1 服务说明

Nacos 作为服务注册中心和配置中心,采用 3 节点集群模式部署,使用 MySQL 作为持久化存储。

项目
Stack 名称 review_nacos
镜像 nacos/nacos-server:v2.3.0
集群模式 cluster
数据库 MySQL (review-tool-mysql-master)

12.2 节点分布

服务 部署节点 Web 端口 gRPC 客户端端口 gRPC 集群端口
nacos1 ZD-BAK-APP1 21848 22848 22849
nacos2 ZD-BAK-APP2 23848 24848 24849
nacos3 zd-bak-app3 25848 26848 26849

12.3 部署前准备

创建 Nacos 数据库

# 连接 MySQL Master
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@'

# 创建数据库
CREATE DATABASE nacos CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

# 授权用户
GRANT ALL PRIVILEGES ON nacos.* TO 'zd_tool'@'%';
FLUSH PRIVILEGES;

exit;

导入 Nacos Schema

# 导入建表脚本
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' nacos < /path/to/nacos-cluser/mysql-schema.sql

拉取镜像

在每个目标节点上拉取镜像:

docker pull nacos/nacos-server:v2.3.0

12.4 部署命令

cd /path/to/docker-swarm-review/nacos-cluser

# 转换 Windows 换行符
sed -i 's/\r$//' cluster-docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./cluster-docker-compose.yml | docker stack deploy --compose-file - review_nacos

12.5 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_nacos

# 预期输出:
# review_nacos_nacos1   replicated   1/1
# review_nacos_nacos2   replicated   1/1
# review_nacos_nacos3   replicated   1/1

# 查看详细状态
docker stack ps review_nacos --no-trunc | grep -v Shutdown

检查集群状态

通过 API 检查集群节点:

curl -X GET "http://ZD-BAK-APP1:21848/nacos/v1/ns/operator/cluster/nodes"

或登录 Web 控制台,进入 集群管理 -> 节点列表 查看 3 个节点是否都处于 UP 状态。

12.6 连接信息

项目
控制台 (nacos1) http://ZD-BAK-APP1:21848/nacos
控制台 (nacos2) http://ZD-BAK-APP2:23848/nacos
控制台 (nacos3) http://zd-bak-app3:25848/nacos
用户名 nacos
密码 nacos

12.7 端口说明

端口 用途
8848 Web 控制台和 HTTP API
9848 gRPC 客户端通信端口
9849 gRPC 集群节点间通信端口

注意:客户端连接时需配置 8848 端口gRPC 端口会自动计算(默认 8848+1000

12.8 常用运维命令

# 查看服务日志
docker service logs review_nacos_nacos1 --tail 50
docker service logs review_nacos_nacos2 --tail 50
docker service logs review_nacos_nacos3 --tail 50

# 强制重启服务
docker service update --force review_nacos_nacos1

# 删除并重新部署
docker stack rm review_nacos
sleep 15
env $(cat ./env_review | xargs) envsubst < ./cluster-docker-compose.yml | docker stack deploy --compose-file - review_nacos

12.9 常见问题

Nacos 启动失败No DataSource set

原因: 数据库连接失败或用户没有权限

解决:

# 检查数据库连接
mysql -h ZD-BAK-APP1 -P 25306 -uzd_tool -p'gkxl2024#@' -e "USE nacos; SHOW TABLES;"

# 如果权限不足,重新授权
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' -e "GRANT ALL PRIVILEGES ON nacos.* TO 'zd_tool'@'%'; FLUSH PRIVILEGES;"

# 重启 Nacos 服务
docker service update --force review_nacos_nacos1
docker service update --force review_nacos_nacos2
docker service update --force review_nacos_nacos3

集群节点无法发现彼此

原因: 节点间网络不通或 gRPC 端口未开放

解决:

# 确保防火墙已关闭
systemctl stop firewalld
systemctl disable firewalld

# 或开放 Nacos 所需端口
firewall-cmd --permanent --add-port=8848/tcp
firewall-cmd --permanent --add-port=9848/tcp
firewall-cmd --permanent --add-port=9849/tcp
firewall-cmd --reload

十四、部署 XXL-Job-Admin

13.1 服务说明

XXL-Job 是分布式任务调度平台Admin 为调度中心。

项目
Stack 名称 review_xxl_job
镜像 xuxueli/xxl-job-admin:2.4.1
端口 9991
副本数 2
数据库 MySQL (review-tool-mysql-master)

13.2 部署前准备

创建 xxl_job 数据库

# 连接 MySQL Master
mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@'

# 创建数据库
CREATE DATABASE xxl_job CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

# 授权用户
GRANT ALL PRIVILEGES ON xxl_job.* TO 'zd_tool'@'%';
FLUSH PRIVILEGES;

exit;

导入 XXL-Job Schema

从官方获取建表脚本:https://github.com/xuxueli/xxl-job/blob/master/doc/db/tables_xxl_job.sql

mysql -h ZD-BAK-APP1 -P 25306 -uroot -p'gkxl2024#@' xxl_job < tables_xxl_job.sql

添加节点标签

# 选择 2 个节点运行 xxl-job-admin
docker node update --label-add xxl_job_admin=1 ZD-BAK-APP1
docker node update --label-add xxl_job_admin=1 ZD-BAK-APP2

# 验证标签
docker node ls -q | xargs -I {} docker node inspect {} --format '{{.Description.Hostname}}: {{.Spec.Labels}}'

拉取镜像

在目标节点上拉取镜像:

docker pull xuxueli/xxl-job-admin:2.4.1

13.3 部署命令

cd /path/to/docker-swarm-review/xxl-job-admin

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_xxl_job

13.4 验证部署

# 查看服务状态
docker service ls | grep review_xxl_job

# 预期输出:
# review_xxl_job_server   replicated   2/2

# 查看详细状态
docker stack ps review_xxl_job --no-trunc | grep -v Shutdown

13.5 连接信息

项目
控制台 http://<节点IP>:9991/xxl-job-admin
用户名 admin
密码 123456

13.6 常用运维命令

# 查看服务日志
docker service logs review_xxl_job_server --tail 50

# 强制重启服务
docker service update --force review_xxl_job_server

# 删除并重新部署
docker stack rm review_xxl_job
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_xxl_job

13.7 常见问题

原因: 无法连接 MySQL 数据库

解决:

# 检查数据库连接
mysql -h ZD-BAK-APP1 -P 25306 -uzd_tool -p'gkxl2024#@' -e "USE xxl_job; SHOW TABLES;"

# 确认 env_review 中的数据库配置正确
cat env_review | grep DATASOURCE

启动失败Table 'xxl_job.xxl_job_info' doesn't exist

原因: 数据库 Schema 未导入

解决: 导入 XXL-Job 官方建表脚本


十五、部署 Canal

14.1 服务说明

Canal 是阿里巴巴开源的 MySQL binlog 增量订阅&消费组件,用于将 MySQL 数据变更实时同步到其他系统。

项目
Stack 名称 review_canal
镜像 canal/canal-server:v1.1.5
数据源 192.168.3.123:3306
输出模式 RabbitMQ
部署节点 ZD-BAK-APP2

14.2 部署前准备

确保 MySQL 开启 binlog

Canal 依赖 MySQL 的 binlog需确保数据源 MySQL 已开启:

-- 检查 binlog 是否开启
SHOW VARIABLES LIKE 'log_bin';

-- 检查 binlog 格式(需要为 ROW
SHOW VARIABLES LIKE 'binlog_format';

确保 MySQL 用户有复制权限

Canal 用户需要以下权限:

  • SELECT - 查询表结构
  • REPLICATION SLAVE - 读取 binlog
  • REPLICATION CLIENT - 查看 binlog 状态
-- Canal 使用的用户需要有以下权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

验证 MySQL 用户权限

由于部署服务器上没有安装 mysql 客户端,使用已部署的 MySQL 容器来测试连接:

# 通过 MySQL Master 容器连接外部 MySQL 验证权限
docker exec $(docker ps -q -f name=review_tool_mysql_mysql-master) mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SELECT 1; SHOW GRANTS; SHOW MASTER STATUS"

预期输出:

1
1
Grants for repl@%
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `repl`@`%`
File    Position        Binlog_Do_DB    Binlog_Ignore_DB        Executed_Gtid_Set
mysql-bin.015149        132458641       zd_rescue,xxl-job,nacos_job     information_schema,mysql,performance_schema,sys

如果权限不足(缺少 SELECT 或 REPLICATION CLIENT需要在 MySQL 源服务器上执行授权。

添加节点标签

docker node update --label-add review_canal=1 ZD-BAK-APP2

# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'

拉取镜像

方法一:直接从 Docker Hub 拉取(如网络可达)

docker pull canal/canal-server:v1.1.5

方法二:通过私有仓库中转(推荐)

如果目标服务器无法访问 Docker Hub可通过私有仓库 harbor.sino-assist.com 中转:

# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上拉取并推送到私有仓库
docker pull canal/canal-server:v1.1.5
docker tag canal/canal-server:v1.1.5 harbor.sino-assist.com/library/canal-server:v1.1.5
docker login harbor.sino-assist.com
docker push harbor.sino-assist.com/library/canal-server:v1.1.5

# 2. 在目标服务器132/ZD-BAK-APP2上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/library/canal-server:v1.1.5
docker tag harbor.sino-assist.com/library/canal-server:v1.1.5 canal/canal-server:v1.1.5

14.3 配置说明

env_review 配置项:

配置项 说明
canal_instance_master_address 192.168.3.123:3306 MySQL 数据源地址
canal_instance_dbUsername repl MySQL 用户名
canal_instance_dbPassword nczl@sino_db MySQL 密码
canal_instance_filter_regex zd_rescue.* 订阅的表正则表达式
canal_mq_topic canal_mysql_bin MQ Topic 名称
rabbitmq_host review-rabbitmq-stats:5672 RabbitMQ 地址overlay 网络内)
rabbitmq_exchange canal_exchange RabbitMQ Exchange
rabbitmq_username root RabbitMQ 用户名
rabbitmq_password gkxl650 RabbitMQ 密码
rabbitmq_virtual_host review RabbitMQ VHost

14.4 部署命令

cd /path/to/docker-swarm-review/canal

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal

14.5 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_canal

# 预期输出:
# review_canal_db   replicated   1/1

# 查看详细状态
docker stack ps review_canal --no-trunc | grep -v Shutdown

查看日志

docker service logs review_canal_db --tail 100

日志中应能看到:

  • destination:example start successful 表示启动成功
  • position:xxx 表示已连接 MySQL 并获取 binlog 位置

验证 RabbitMQ 接收

登录 RabbitMQ 管理界面,检查:

  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 常用运维命令

# 查看服务日志
docker service logs review_canal_db --tail 50

# 强制重启服务
docker service update --force review_canal_db

# 删除并重新部署
docker stack rm review_canal
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal

14.8 常见问题

启动失败Authentication failed

原因: MySQL 用户名或密码错误,或用户没有复制权限

解决:

# 检查 MySQL 连接
mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SHOW MASTER STATUS"

# 如果连接失败,在 MySQL 源服务器上授权
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

启动失败Could not find first log file name in binary log index file

原因: binlog 文件不存在或已被清理

解决:

# 检查 MySQL binlog 状态
mysql -h 192.168.3.123 -P 3306 -urepl -p'nczl@sino_db' -e "SHOW MASTER STATUS; SHOW BINARY LOGS;"

# 如果 binlog 被清理,需要重新初始化 Canal
docker stack rm review_canal
# 删除 Canal 数据卷(如有)
docker volume ls | grep canal | awk '{print $2}' | xargs docker volume rm
# 重新部署

无法连接 RabbitMQ

原因: RabbitMQ 地址错误或网络不通

解决:

# 确保 Canal 和 RabbitMQ 在同一 overlay 网络
docker network inspect review

# 测试网络连通性(在 Canal 容器内)
docker exec $(docker ps -q -f name=review_canal_db) ping review-rabbitmq-stats

# 检查 RabbitMQ VHost 是否存在
docker exec $(docker ps -q -f name=review_rabbitmq_stats) rabbitmqctl list_vhosts

15. FastDFS

15.1 服务说明

项目 说明
镜像 harbor.sino-assist.com/season/fastdfs:1.2
部署方式 Docker Compose非 Swarm
Tracker 端口 22122
Storage 端口 23000
HTTP 访问端口 8088
数据目录 /opt/fastdfs/
当前部署机器 192.168.1.171

15.2 目录结构

/opt/fastdfs/
├── docker-compose.yml      # 容器编排配置
├── storage.conf            # storage 配置(挂载进容器)
├── nginx.conf              # nginx 配置(挂载进容器)
├── tracker_data/           # tracker 数据目录
├── storage_base_path/      # storage base 目录
└── store_path0/            # 文件实际存储目录

15.3 docker-compose.yml

version: '3'
services:
  tracker:
    image: harbor.sino-assist.com/season/fastdfs:1.2
    container_name: tracker
    network_mode: host
    restart: always
    volumes:
      - "./tracker_data:/fastdfs/tracker/data"
    command: "tracker"

  storage:
    image: harbor.sino-assist.com/season/fastdfs:1.2
    container_name: storage
    network_mode: host
    restart: always
    volumes:
      - "./storage.conf:/fdfs_conf/storage.conf"
      - "./storage_base_path:/fastdfs/storage/data"
      - "./store_path0:/fastdfs/store_path"
    environment:
      TRACKER_SERVER: "<本机IP>:22122"
    command: "storage"

  nginx:
    image: harbor.sino-assist.com/season/fastdfs:1.2
    container_name: fdfs-nginx
    network_mode: host
    restart: always
    volumes:
      - "./nginx.conf:/etc/nginx/conf/nginx.conf"
      - "./store_path0:/fastdfs/store_path"
    environment:
      TRACKER_SERVER: "<本机IP>:22122"
    command: "nginx"

迁移到新机器时,将 TRACKER_SERVER 中的 IP 替换为新机器 IP。

15.4 storage.conf 关键配置

base_path=/fastdfs/storage
store_path_count=1
store_path0=/fastdfs/store_path
tracker_server=<本机IP>:22122
http.server_port=8888

15.5 nginx.conf 说明

  • 监听端口:8088
  • 文件访问路径:/group1/M00
  • 文件存储根目录:/fastdfs/storage/data(容器内路径)
  • 已配置跨域 Access-Control-Allow-Origin: *

15.6 常用运维命令

# 启动
cd /opt/fastdfs && docker compose up -d

# 停止
cd /opt/fastdfs && docker compose stop

# 查看状态
docker ps | grep -E 'tracker|storage|nginx'

# 查看日志
docker logs tracker
docker logs storage
docker logs fdfs-nginx

# 验证端口
ss -tlnp | grep -E '22122|23000|8088'

15.7 迁移步骤

适用于将 FastDFS 从一台机器迁移到另一台机器(可停服)。

1. 在目标机器安装 Docker

CentOS 7 需先修复官方源(已停止维护):

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all && yum makecache

yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker && systemctl start docker

Docker Engine 26.x 已内置 Compose 插件,无需单独安装 docker-compose。使用 docker compose(中间空格)而非 docker-compose

配置镜像加速(参考 dongyubin/DockerHub

cat > /etc/docker/daemon.json <<'EOF'
{
  "registry-mirrors": [
    "https://docker.1panel.live",
    "https://hub.rat.dev",
    "https://dockerpull.org",
    "https://dockerhub.icu"
  ]
}
EOF

systemctl daemon-reload && systemctl restart docker

2. 登录 harbor

docker login harbor.sino-assist.com

3. 在源机器停服并同步数据

# 停止容器
cd /opt/fastdfs && docker compose stop

# 后台同步数据到目标机器
nohup rsync -avz --progress /opt/fastdfs/ root@<目标IP>:/opt/fastdfs/ > /tmp/rsync_fastdfs.log 2>&1 &

# 查看同步进度
tail -f /tmp/rsync_fastdfs.log

# 确认同步完成
ps aux | grep rsync
tail -20 /tmp/rsync_fastdfs.log

4. 修改目标机器配置中的 IP

# 修改 storage.conf
sed -i 's/tracker_server=<源IP>:22122/tracker_server=<目标IP>:22122/' /opt/fastdfs/storage.conf

# 修改 docker-compose.yml
sed -i 's/<源IP>/<目标IP>/g' /opt/fastdfs/docker-compose.yml

5. 启动并验证

cd /opt/fastdfs && docker compose up -d

# 验证容器状态
docker ps

# 验证端口
ss -tlnp | grep -E '22122|23000|8088'

# 验证文件访问
curl http://<目标IP>:8088/group1/M00/<文件路径>

6. 修改上层应用配置

将应用中 FastDFS 地址从源机器 IP 改为目标机器 IP端口不变。

数据未同步到 RabbitMQ

原因: 订阅的表正则表达式不匹配

解决:

# 检查 env_review 中的 canal_instance_filter_regex 配置
# 格式schema.table支持正则
# 示例zd_rescue\\..*  订阅 zd_rescue 库下所有表

# 修改配置后重新部署
docker stack rm review_canal
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_canal

十六、部署 Elasticsearch + Kibana

15.1 服务说明

Elasticsearch 是分布式搜索和分析引擎Kibana 是其可视化平台。本环境用于日志存储和分析。

项目
Stack 名称 review_log_es
ES 镜像 bitnami/elasticsearch:8.13.4
Kibana 镜像 bitnami/kibana:8.13.4
ES HTTP 端口 9200
ES Transport 端口 9300
Kibana 端口 5601
ES 堆内存 8192m
部署节点 标签 review_es=1 的节点

15.2 部署前准备

添加节点标签

选择部署 ES 的节点(建议选择内存较大的节点):

# 将 ES 部署到 ZD-BAK-APP2
docker node update --label-add review_es=1 ZD-BAK-APP2

# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'

创建数据目录

在目标节点上创建数据目录并设置权限:

# 创建目录
mkdir -p /mnt/data/volumes/elasticsearch
mkdir -p /mnt/data/volumes/kibana/data
mkdir -p /mnt/data/volumes/kibana/config

# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chown -R 1001:1001 /mnt/data/volumes/kibana

目录说明:

目录 用途
/mnt/data/volumes/elasticsearch ES 数据存储
/mnt/data/volumes/kibana/data Kibana 数据存储
/mnt/data/volumes/kibana/config Kibana 配置文件

调整系统参数

ES 需要调整 vm.max_map_count 参数:

# 临时生效
sysctl -w vm.max_map_count=262144

# 永久生效
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p

# 验证
sysctl vm.max_map_count

拉取镜像

方法一:直接从 Docker Hub 拉取

docker pull bitnami/elasticsearch:8.13.4
docker pull bitnami/kibana:8.13.4

方法二:通过私有仓库中转(推荐)

# 1. 在能访问 Docker Hub 的服务器(如 50 服务器)上操作
docker pull bitnami/elasticsearch:8.13.4
docker tag bitnami/elasticsearch:8.13.4 harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker push harbor.sino-assist.com/bitnami/elasticsearch:8.13.4

docker pull bitnami/kibana:8.13.4
docker tag bitnami/kibana:8.13.4 harbor.sino-assist.com/bitnami/kibana:8.13.4
docker push harbor.sino-assist.com/bitnami/kibana:8.13.4

# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker tag harbor.sino-assist.com/bitnami/elasticsearch:8.13.4 bitnami/elasticsearch:8.13.4

docker pull harbor.sino-assist.com/bitnami/kibana:8.13.4
docker tag harbor.sino-assist.com/bitnami/kibana:8.13.4 bitnami/kibana:8.13.4

创建 Kibana 配置文件(可选)

如需自定义 Kibana 配置,创建配置文件:

cat > /mnt/data/volumes/kibana/config/kibana.yml << 'EOF'
i18n.locale: "zh-CN"
path:
  data: /bitnami/kibana/data
pid:
  file: /opt/bitnami/kibana/tmp/kibana.pid
server:
  host: 0.0.0.0
  port: 5601
elasticsearch:
  hosts: http://review-es-elasticsearch:9200
EOF

chown 1001:1001 /mnt/data/volumes/kibana/config/kibana.yml

15.3 配置文件说明

env_review 配置项:

配置项 说明
NAMESPACE review 命名空间,用于网络和主机名
NODE_PORT 9200 ES HTTP 端口
NODE_PORT_2 9300 ES Transport 端口
NODE_PORT_KIBANA 5601 Kibana Web 端口

docker-compose.yml 关键配置:

配置项 说明
ELASTICSEARCH_HEAP_SIZE 8192m ES JVM 堆内存大小
hostname ${NAMESPACE}-es-elasticsearch ES 容器主机名
KIBANA_ELASTICSEARCH_URL ${NAMESPACE}-es-elasticsearch Kibana 连接的 ES 地址

15.3.1 安装 IK 分词器(可选)

IK 分词器是 Elasticsearch 的中文分词插件,版本必须与 ES 版本完全匹配。

创建插件目录

在 ES 部署节点上执行:

# 创建 plugins 目录
mkdir -p /mnt/data/volumes/elasticsearch-plugins

# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/elasticsearch-plugins

下载并安装 IK 分词器

cd /mnt/data/volumes/elasticsearch-plugins

# 下载 IK 分词器(版本 8.13.4 对应 ES 8.13.4
curl -L -o analysis-ik.zip https://get.infini.cloud/elasticsearch/analysis-ik/8.13.4

# 解压到 analysis-ik 目录(使用 Python
python3 -c "import zipfile; zipfile.ZipFile('analysis-ik.zip').extractall('analysis-ik')"

# 删除压缩包
rm -f analysis-ik.zip

# 设置权限
chown -R 1001:1001 analysis-ik

注意IK 分词器版本必须与 Elasticsearch 版本完全一致。如果 ES 升级,需要同步更新 IK 分词器版本。

验证 IK 分词器安装

部署完成后,验证插件是否加载:

# 查看已安装的插件
curl http://<ES节点IP>:9200/_cat/plugins?v

# 预期输出应包含 analysis-ik

# 测试 IK 分词器ik_max_word 细粒度分词)
curl -X POST "http://<ES节点IP>:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
  "analyzer": "ik_max_word",
  "text": "中华人民共和国国歌"
}'

# 测试 IK 分词器ik_smart 粗粒度分词)
curl -X POST "http://<ES节点IP>:9200/_analyze?pretty" -H 'Content-Type: application/json' -d'
{
  "analyzer": "ik_smart",
  "text": "中华人民共和国国歌"
}'

IK 分词模式说明:

分词器 说明 示例
ik_max_word 细粒度分词,穷尽所有可能的组合 "中华人民共和国" → 中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、共和、国
ik_smart 粗粒度分词,做最少切分 "中华人民共和国" → 中华人民共和国

15.4 部署命令

cd /path/to/docker-swarm-review/elasticsearch

# 转换 Windows 换行符(如果文件从 Windows 传输)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es

15.5 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_log_es

# 预期输出:
# review_log_es_elasticsearch   replicated   1/1
# review_log_es_kibana          replicated   1/1

# 查看详细状态
docker stack ps review_log_es --no-trunc | grep -v Shutdown

测试 ES 连通性

注意localhost 只能在 ES 实际运行的节点上使用。推荐使用节点 IP 或容器内测试。

# 方法1通过节点 IP 测试(推荐,任意位置可执行)
# 将 <ES节点IP> 替换为 ES 实际运行节点的 IP
curl http://<ES节点IP>:9200

# 方法2通过容器内部测试在 ES 所在节点执行)
docker exec $(docker ps -q -f name=review_log_es_elasticsearch) curl -s http://localhost:9200

# 方法3在 ES 所在节点上使用 localhost
curl http://localhost:9200

# 预期输出JSON 格式):
# {
#   "name" : "review-es-elasticsearch",
#   "cluster_name" : "elasticsearch",
#   "cluster_uuid" : "xxx",
#   "version" : {
#     "number" : "8.13.4",
#     ...
#   },
#   "tagline" : "You Know, for Search"
# }

# 检查集群健康状态
curl http://<ES节点IP>:9200/_cluster/health?pretty

# 检查节点信息
curl http://<ES节点IP>:9200/_cat/nodes?v

确认 ES 运行节点

# 查看 ES 实际运行在哪个节点
docker service ps review_log_es_elasticsearch --format "table {{.Node}}\t{{.CurrentState}}"

测试 Kibana 连通性

注意:同 ESlocalhost 只能在 Kibana 实际运行的节点上使用。

# 方法1通过节点 IP 测试(推荐)
curl http://<Kibana节点IP>:5601/api/status

# 方法2通过容器内部测试
docker exec $(docker ps -q -f name=review_log_es_kibana) curl -s http://localhost:5601/api/status

# 浏览器访问
# http://<节点IP>:5601

15.6 连接信息

服务 地址 说明
Elasticsearch HTTP http://<节点IP>:9200 REST API 接口
Elasticsearch Transport <节点IP>:9300 节点间通信
Kibana http://<节点IP>:5601 Web 可视化界面
ES 内部地址 review-es-elasticsearch:9200 overlay 网络内访问

15.7 常用运维命令

# 查看 ES 服务日志
docker service logs review_log_es_elasticsearch --tail 100

# 查看 Kibana 服务日志
docker service logs review_log_es_kibana --tail 100

# 强制重启 ES 服务
docker service update --force review_log_es_elasticsearch

# 强制重启 Kibana 服务
docker service update --force review_log_es_kibana

# 进入 ES 容器
docker exec -it $(docker ps -q -f name=review_log_es_elasticsearch) bash

# 进入 Kibana 容器
docker exec -it $(docker ps -q -f name=review_log_es_kibana) bash

# 删除并重新部署
docker stack rm review_log_es
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es

15.8 ES 常用 API

# 查看所有索引
curl http://localhost:9200/_cat/indices?v

# 查看集群健康状态
curl http://localhost:9200/_cluster/health?pretty

# 查看节点状态
curl http://localhost:9200/_cat/nodes?v

# 查看分片分配
curl http://localhost:9200/_cat/shards?v

# 查看集群设置
curl http://localhost:9200/_cluster/settings?pretty

# 创建索引
curl -X PUT "http://localhost:9200/test_index"

# 删除索引
curl -X DELETE "http://localhost:9200/test_index"

# 查看索引映射
curl http://localhost:9200/test_index/_mapping?pretty

15.9 常见问题及解决

15.9.1 ES 启动失败max virtual memory areas vm.max_map_count is too low

问题: ES 日志显示 max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

原因: 系统 vm.max_map_count 参数默认值过低

解决:

# 临时生效
sysctl -w vm.max_map_count=262144

# 永久生效
echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
sysctl -p

# 重启 ES 服务
docker service update --force review_log_es_elasticsearch

15.9.2 ES 启动失败Permission denied

问题: ES 无法写入数据目录,日志显示权限错误

原因: 数据目录权限不正确Bitnami 镜像使用 UID 1001 运行

解决:

# 修正目录权限
chown -R 1001:1001 /mnt/data/volumes/elasticsearch
chmod 755 /mnt/data/volumes/elasticsearch

# 重启 ES 服务
docker service update --force review_log_es_elasticsearch

15.9.3 ES 启动失败Disk usage exceeded flood-stage watermark

问题: ES 拒绝写入,日志显示磁盘空间不足

原因: 磁盘使用率超过 95%flood stage watermark

解决:

# 检查磁盘使用
df -h /mnt/data/volumes/elasticsearch

# 清理旧数据或扩容磁盘

# 临时解除只读限制(不推荐生产环境使用)
curl -X PUT "http://localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.flood_stage": "98%"
  }
}'

# 解除索引只读状态
curl -X PUT "http://localhost:9200/_all/_settings" -H 'Content-Type: application/json' -d'
{
  "index.blocks.read_only_allow_delete": null
}'

15.9.4 curl localhost:9200 无响应,但 IP 可以访问

问题: 在服务器上执行 curl http://localhost:9200 无响应,但 curl http://<节点IP>:9200 可以

原因: localhost 只指向当前机器。如果你在 A 节点执行 curl但 ES 运行在 B 节点localhost 无法访问

解决:

# 1. 确认 ES 运行在哪个节点
docker service ps review_log_es_elasticsearch --format "table {{.Node}}\t{{.CurrentState}}"

# 2. 使用正确的访问方式:
# - 在 ES 所在节点:可以用 localhost
# - 在其他节点:必须用 ES 节点的 IP
curl http://<ES节点IP>:9200

# 3. 或通过容器内部测试(在 ES 所在节点执行)
docker exec $(docker ps -q -f name=review_log_es_elasticsearch) curl -s http://localhost:9200

说明: 只要通过 IP 能访问ES 部署就是成功的。这是正常现象,不是故障。

15.9.5 Kibana 启动失败Unable to retrieve version information from Elasticsearch

问题: Kibana 无法连接 ES

原因: ES 尚未启动完成,或网络不通

解决:

# 检查 ES 是否正常运行(使用节点 IP
curl http://<ES节点IP>:9200

# 检查网络连通性(在 Kibana 容器内)
docker exec $(docker ps -q -f name=review_log_es_kibana) curl review-es-elasticsearch:9200

# 如果 ES 正常但 Kibana 无法连接,检查 overlay 网络
docker network inspect review

# 重启 Kibana 服务
docker service update --force review_log_es_kibana

15.9.6 Kibana 启动失败FATAL Error: Port 5601 is already in use

问题: Kibana 端口被占用

原因: 端口被其他进程占用

解决:

# 检查端口占用
netstat -tlnp | grep 5601
# 或
ss -tlnp | grep 5601

# 停止占用端口的进程,或修改 env_review 中的端口配置
# NODE_PORT_KIBANA=5602

# 重新部署
docker stack rm review_log_es
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es

15.9.7 ES 集群状态为 Yellow

问题: curl http://localhost:9200/_cluster/health 返回 status: yellow

原因: 单节点部署时,副本分片无法分配(正常现象)

解决:

单节点环境下yellow 状态是正常的。如需变为 green

# 将所有索引的副本数设为 0
curl -X PUT "http://localhost:9200/_settings" -H 'Content-Type: application/json' -d'
{
  "index": {
    "number_of_replicas": 0
  }
}'

15.9.8 ES 内存不足 (OOM)

问题: ES 进程被 OOM Killer 杀死

原因: 堆内存设置过大,超过系统可用内存

解决:

# 检查系统内存
free -h

# 修改 docker-compose.yml 中的堆内存设置
# ELASTICSEARCH_HEAP_SIZE=4096m  # 根据实际情况调整

# 重新部署
docker stack rm review_log_es
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log_es

堆内存建议:

  • 堆内存不超过物理内存的 50%
  • 堆内存不超过 32GBJVM 压缩指针限制)
  • 为操作系统和文件缓存预留足够内存

15.9.9 无法拉取镜像

问题: dial tcp xxx:443: i/o timeoutno such image

原因: 无法访问 Docker Hub

解决: 参考 9.1 节「镜像拉取失败」,通过私有仓库中转

# 在能访问外网的服务器上
docker pull bitnami/elasticsearch:8.13.4
docker tag bitnami/elasticsearch:8.13.4 harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker push harbor.sino-assist.com/bitnami/elasticsearch:8.13.4

# 在目标服务器上
docker pull harbor.sino-assist.com/bitnami/elasticsearch:8.13.4
docker tag harbor.sino-assist.com/bitnami/elasticsearch:8.13.4 bitnami/elasticsearch:8.13.4

15.9.10 Kibana 界面显示英文

问题: Kibana 界面为英文

原因: 未配置中文语言

解决:

创建或修改 Kibana 配置文件:

# 添加中文配置
echo 'i18n.locale: "zh-CN"' >> /mnt/data/volumes/kibana/config/kibana.yml

# 重启 Kibana
docker service update --force review_log_es_kibana

十七、部署 Log 服务Logstash + Filebeat

16.1 服务说明

Log 服务用于收集、处理和存储应用日志,由 Filebeat日志收集和 Logstash日志处理组成。

项目
Stack 名称 review_log
Logstash 镜像 docker.elastic.co/logstash/logstash:8.13.4
Filebeat 镜像 docker.elastic.co/beats/filebeat:8.13.4
Logstash 端口 5044
日志卷 review_logs
依赖 Elasticsearch

16.2 架构说明

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Filebeat   │────▶│   Logstash   │────▶│ Elasticsearch│
│  (每个节点)   │     │  (ES 节点)    │     │              │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                    │
   收集日志            过滤/转换             存储/索引
   /logs/*/*.log

组件说明:

组件 部署模式 说明
Filebeat global每个节点一个 轻量级日志收集器,监控 /logs 目录
Logstash replicated部署在 ES 节点) 日志处理管道,解析、过滤、转换日志

16.3 部署前准备

创建日志卷

# 创建外部卷用于收集应用日志
docker volume create review_logs

# 验证
docker volume ls | grep review_logs

重要:此卷需要在所有节点上创建,用于应用服务挂载日志目录。

拉取镜像

方法一:直接从 Elastic 官方拉取

# 在所有节点上执行(因为 Filebeat 是全局部署)
docker pull docker.elastic.co/logstash/logstash:8.13.4
docker pull docker.elastic.co/beats/filebeat:8.13.4

方法二:通过私有仓库中转(推荐)

# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull docker.elastic.co/logstash/logstash:8.13.4
docker tag docker.elastic.co/logstash/logstash:8.13.4 harbor.sino-assist.com/elastic/logstash:8.13.4
docker push harbor.sino-assist.com/elastic/logstash:8.13.4

docker pull docker.elastic.co/beats/filebeat:8.13.4
docker tag docker.elastic.co/beats/filebeat:8.13.4 harbor.sino-assist.com/elastic/filebeat:8.13.4
docker push harbor.sino-assist.com/elastic/filebeat:8.13.4

# 2. 在所有目标节点上从私有仓库拉取
docker login harbor.sino-assist.com

docker pull harbor.sino-assist.com/elastic/logstash:8.13.4
docker tag harbor.sino-assist.com/elastic/logstash:8.13.4 docker.elastic.co/logstash/logstash:8.13.4

docker pull harbor.sino-assist.com/elastic/filebeat:8.13.4
docker tag harbor.sino-assist.com/elastic/filebeat:8.13.4 docker.elastic.co/beats/filebeat:8.13.4

注意Filebeat 是全局部署(每个节点一个),所以需要在所有 Swarm 节点上拉取 Filebeat 镜像。

16.4 配置文件说明

env_review 配置项

配置项 说明
NAMESPACE review 命名空间
NODE_PORT 5044 Logstash 监听端口

logstash.conf 说明

filter {
    grok {
        # 解析日志格式:时间戳 [服务名] [TID:链路ID] [线程] 日志级别 类名 - 消息
        match => { "message" => "..." }
    }
    date {
        # 将日志时间戳设置为 @timestamp
    }
    mutate {
        # 清理多余字段
    }
}

output {
    elasticsearch {
        hosts => [ "review-es-elasticsearch:9200" ]
        index => "sslog-%{[service]}"  # 按服务名创建索引
    }
}

filebeat.yml 说明

配置项 说明
paths /logs//.log 监控的日志文件路径
multiline.pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2} 多行日志合并(以日期开头为新日志)
output.logstash.hosts review-log-logstash:5044 Logstash 地址

16.5 部署命令

cd /path/to/docker-swarm-review/log

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review
sed -i 's/\r$//' logstash.conf
sed -i 's/\r$//' filebeat.yml

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log

16.6 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_log

# 预期输出:
# review_log_logstash   replicated   1/1
# review_log_filebeat   global       3/3  (取决于节点数)

# 查看详细状态
docker stack ps review_log --no-trunc | grep -v Shutdown

查看服务日志

# 查看 Logstash 日志
docker service logs review_log_logstash --tail 50

# 查看 Filebeat 日志
docker service logs review_log_filebeat --tail 50

验证日志流转

# 检查 ES 中是否有日志索引
curl http://<ES节点IP>:9200/_cat/indices?v | grep sslog

# 预期输出(有应用日志时):
# yellow open sslog-service1 xxx 1 1 100 0 50kb 50kb

16.7 连接信息

服务 地址 说明
Logstash review-log-logstash:5044 overlay 网络内部地址
日志卷 review_logs:/logs 应用日志挂载点
ES 索引 sslog-{service} 日志索引命名规则

16.8 应用接入说明

业务应用需要将日志输出到 review_logs 卷中,才能被 Filebeat 收集。

应用 docker-compose.yml 示例

services:
  my-service:
    image: my-app:latest
    volumes:
      - review_logs:/logs
    # 应用日志输出到 /logs/my-service/xxx.log

volumes:
  review_logs:
    external: true

日志格式要求

为了 Logstash 正确解析,应用日志应遵循以下格式:

2024-01-01 12:00:00.000 [service-name] [TID:trace-id] [thread-name] INFO com.example.Class - Log message

16.9 常用运维命令

# 查看 Logstash 日志
docker service logs review_log_logstash --tail 100

# 查看 Filebeat 日志
docker service logs review_log_filebeat --tail 100

# 进入 Logstash 容器
docker exec -it $(docker ps -q -f name=review_log_logstash) bash

# 进入 Filebeat 容器(指定节点)
docker exec -it $(docker ps -q -f name=review_log_filebeat) bash

# 强制重启 Logstash
docker service update --force review_log_logstash

# 强制重启 Filebeat所有节点
docker service update --force review_log_filebeat

# 删除并重新部署
docker stack rm review_log
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log

16.10 日志流转详解

完整数据流向图

┌──────────────────────────────────────────────────────────────────────────────┐
│                           Docker Swarm 集群                                  │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                     Docker Volume: review_logs                          │ │
│  │                                                                         │ │
│  │   /logs/sa-gateway/sa-gateway.log                                       │ │
│  │   /logs/sa-auth/sa-auth.log                                             │ │
│  │   /logs/sa-system/sa-system.log                                         │ │
│  │                                                                         │ │
│  └───────────────────────────────┬─────────────────────────────────────────┘ │
│                                  │                                           │
│                                  │ 读取                                       │
│                                  ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                        Filebeat (每节点一个)                             │ │
│  │                                                                         │ │
│  │   - 监控 /logs/*/*.log                                                  │ │
│  │   - 多行日志合并(异常堆栈等)                                           │ │
│  │   - 发送到 Logstash:5044                                                │ │
│  │                                                                         │ │
│  └───────────────────────────────┬─────────────────────────────────────────┘ │
│                                  │                                           │
│                                  │ TCP 5044                                  │
│                                  ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                           Logstash                                      │ │
│  │                                                                         │ │
│  │   - Grok 解析日志格式                                                    │ │
│  │   - 提取字段: service, tid, thread, loglevel, class, message            │ │
│  │   - 时间戳转换                                                          │ │
│  │   - 输出到 Elasticsearch                                                │ │
│  │                                                                         │ │
│  └───────────────────────────────┬─────────────────────────────────────────┘ │
│                                  │                                           │
│                                  │ HTTP 9200                                 │
│                                  ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                        Elasticsearch                                    │ │
│  │                                                                         │ │
│  │   索引:                                                                  │ │
│  │   - sslog-sa-gateway                                                    │ │
│  │   - sslog-sa-auth                                                       │ │
│  │   - sslog-sa-system                                                     │ │
│  │   - sslog-default解析失败的日志                                      │ │
│  │                                                                         │ │
│  └───────────────────────────────┬─────────────────────────────────────────┘ │
│                                  │                                           │
│                                  │ HTTP 5601                                 │
│                                  ▼                                           │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                           Kibana                                        │ │
│  │                                                                         │ │
│  │   - 可视化查询                                                          │ │
│  │   - 日志检索                                                            │ │
│  │   - 仪表盘                                                              │ │
│  │                                                                         │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

Grok 解析示例

原始日志:

2025-12-23 10:30:45.123  [sa-gateway] [TID:abc123def456] [http-nio-8080-exec-1] INFO  c.s.g.filter.AuthFilter - 用户登录成功

Grok 表达式:

%{TIMESTAMP_ISO8601:oldtimestamp}\s+\[%{DATA:service}\]\s+\[TID:%{NOTSPACE:tid}\]\s+\[%{DATA:thread}\]\s+%{LOGLEVEL:loglevel}\s+%{NOTSPACE:class}\s+-%{GREEDYDATA:oldmessage}

解析后的字段:

字段 说明
@timestamp 2025-12-23T10:30:45.123+08:00 日志时间(转换后)
service sa-gateway 服务名(用于创建索引)
tid abc123def456 SkyWalking Trace ID
thread http-nio-8080-exec-1 线程名
loglevel INFO 日志级别
class c.s.g.filter.AuthFilter 类名
message 用户登录成功 日志内容

SkyWalking Trace ID 关联

日志中的 TID 字段就是 SkyWalking 的 Trace ID由 SkyWalking Logback 插件自动注入。

配置要求: 应用需要引入 SkyWalking Logback 依赖:

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
</dependency>

关联查询:

  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

常用查询语法

# 查询某个服务的日志
service: "sa-gateway"

# 查询某个 Trace ID 的所有日志(跨服务追踪)
tid: "abc123def456"

# 查询错误日志
loglevel: "ERROR"

# 查询某个服务的错误日志
service: "sa-gateway" AND loglevel: "ERROR"

# 查询包含特定关键词的日志
message: "登录失败"

# 查询某个时间范围内的错误
loglevel: "ERROR" AND @timestamp >= "2025-12-23T10:00:00"

# 排除某些日志
service: "sa-gateway" AND NOT message: "健康检查"

日志级别查询

级别 查询语法 说明
ERROR loglevel: "ERROR" 错误日志
WARN loglevel: "WARN" 警告日志
INFO loglevel: "INFO" 信息日志
DEBUG loglevel: "DEBUG" 调试日志

16.12 常见问题及解决

16.12.1 Filebeat 启动失败No such volume

问题: Filebeat 无法启动,提示找不到卷

原因: review_logs 卷未创建

解决:

# 创建卷
docker volume create review_logs

# 重启服务
docker service update --force review_log_filebeat

16.10.2 Logstash 无法连接 Elasticsearch

问题: Logstash 日志显示无法连接 ES

原因: ES 服务未启动或网络不通

解决:

# 检查 ES 是否正常运行
curl http://<ES节点IP>:9200

# 检查网络连通性(在 Logstash 容器内)
docker exec $(docker ps -q -f name=review_log_logstash) curl review-es-elasticsearch:9200

# 确保 Logstash 和 ES 在同一 overlay 网络
docker network inspect review

# 重启 Logstash
docker service update --force review_log_logstash

16.10.3 日志未出现在 ES 中

问题: 应用日志未被收集到 ES

原因:

  • 应用未挂载 review_logs 卷
  • 日志路径不匹配 /logs//.log
  • 日志格式不符合 Logstash grok 规则

解决:

# 1. 检查日志卷是否有内容
docker run --rm -v review_logs:/logs alpine ls -la /logs

# 2. 检查 Filebeat 是否在收集
docker service logs review_log_filebeat --tail 50 | grep -i "harvest"

# 3. 检查 Logstash 是否在处理
docker service logs review_log_logstash --tail 50 | grep -i "event"

16.10.4 Filebeat 镜像拉取失败

问题: 某些节点上 Filebeat 启动失败,提示 No such image

原因: Filebeat 是全局部署,需要所有节点都有镜像

解决:

# 在缺少镜像的节点上拉取
docker pull harbor.sino-assist.com/elastic/filebeat:8.13.4
docker tag harbor.sino-assist.com/elastic/filebeat:8.13.4 docker.elastic.co/beats/filebeat:8.13.4

# 重启服务
docker service update --force review_log_filebeat

16.10.5 Logstash grok 解析失败

问题: 日志写入 ES 但字段未正确解析

原因: 日志格式与 grok 规则不匹配

解决:

# 查看 Logstash 日志中的 grok 解析错误
docker service logs review_log_logstash --tail 100 | grep -i "grok"

# 进入容器测试 grok 规则
docker exec -it $(docker ps -q -f name=review_log_logstash) bash

# 修改 logstash.conf 后重新部署
docker stack rm review_log
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_log

16.10.6 磁盘空间不足

问题: 日志卷占用大量磁盘空间

原因: 日志文件未定期清理

解决:

# 检查卷使用情况
docker system df -v | grep review_logs

# 清理旧日志(在应用层面配置日志轮转)
# 或手动清理
docker run --rm -v review_logs:/logs alpine find /logs -name "*.log" -mtime +7 -delete

十八、部署 SkyWalking

17.1 服务说明

SkyWalking 是一款开源的 APM应用性能监控系统用于分布式系统的链路追踪、性能指标分析和服务拓扑可视化。

项目
Stack 名称 review_skywalking
OAP 镜像 apache/skywalking-oap-server:10.0.0
UI 镜像 apache/skywalking-ui:10.0.0
gRPC 端口 11800 (Agent 上报)
HTTP 端口 12800 (OAP REST API)
UI 端口 18080
存储 Elasticsearch
JVM 内存 2048m
依赖 Elasticsearch

17.2 架构说明

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Java Agent  │────▶│  OAP Server  │────▶│ Elasticsearch│
│  (应用服务)   │     │   :11800     │     │   :9200      │
└──────────────┘     └──────┬───────┘     └──────────────┘
                           │
                     ┌─────▼─────┐
                     │    UI     │
                     │  :18080   │
                     └───────────┘

组件说明:

组件 端口 说明
OAP Server 11800 (gRPC), 12800 (HTTP) 数据收集、分析、存储
UI 18080 Web 可视化界面

17.3 部署前准备

添加节点标签

# 将 SkyWalking 部署到 ZD-BAK-APP2建议与 ES 同节点)
docker node update --label-add review_skywalking=1 ZD-BAK-APP2

# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'

在 ES 中创建索引生命周期策略(可选但推荐)

SkyWalking 会产生大量索引数据,建议配置生命周期策略自动清理旧数据:

# 创建 sw-policy 索引生命周期策略(保留 7 天数据)
curl -X PUT "http://<ES节点IP>:9200/_ilm/policy/sw-policy" -H 'Content-Type: application/json' -d'
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "1d",
            "max_primary_shard_size": "50gb"
          }
        }
      },
      "delete": {
        "min_age": "7d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'

# 验证策略
curl "http://<ES节点IP>:9200/_ilm/policy/sw-policy?pretty"

拉取镜像

方法一:直接拉取

docker pull apache/skywalking-oap-server:10.0.0
docker pull apache/skywalking-ui:10.0.0

方法二:通过私有仓库中转(推荐)

# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull apache/skywalking-oap-server:10.0.0
docker tag apache/skywalking-oap-server:10.0.0 harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0
docker push harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0

docker pull apache/skywalking-ui:10.0.0
docker tag apache/skywalking-ui:10.0.0 harbor.sino-assist.com/apache/skywalking-ui:10.0.0
docker push harbor.sino-assist.com/apache/skywalking-ui:10.0.0

# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0
docker tag harbor.sino-assist.com/apache/skywalking-oap-server:10.0.0 apache/skywalking-oap-server:10.0.0

docker pull harbor.sino-assist.com/apache/skywalking-ui:10.0.0
docker tag harbor.sino-assist.com/apache/skywalking-ui:10.0.0 apache/skywalking-ui:10.0.0

17.4 配置文件说明

env_review 配置项

配置项 说明
NAMESPACE review 命名空间
NODE_PORT 11800 OAP gRPC 端口Agent 上报)
NODE_PORT_2 12800 OAP HTTP 端口REST API
NODE_PORT_UI 18080 UI Web 端口

docker-compose.yml 关键配置

配置项 说明
SW_STORAGE elasticsearch 存储类型
SW_STORAGE_ES_CLUSTER_NODES review-es-elasticsearch:9200 ES 集群地址
SW_HEALTH_CHECKER default 健康检查器
SW_TELEMETRY prometheus 遥测类型
SW_STORAGE_ES_ADVANCED {"index.lifecycle.name":"sw-policy"} ES 索引生命周期策略
JAVA_OPTS -Xms2048m -Xmx2048m JVM 内存设置

17.5 部署命令

cd /path/to/docker-swarm-review/skywalking

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking

17.6 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_skywalking

# 预期输出:
# review_skywalking_oap   replicated   1/1
# review_skywalking_ui    replicated   1/1

# 查看详细状态
docker stack ps review_skywalking --no-trunc | grep -v Shutdown

查看服务日志

# 查看 OAP 日志
docker service logs review_skywalking_oap --tail 100

# 查看 UI 日志
docker service logs review_skywalking_ui --tail 50

# 确认 OAP 启动成功(查找 started 关键字)
docker service logs review_skywalking_oap --tail 100 | grep -i "started"

访问 UI

在浏览器访问:

http://<节点IP>:18080

如果 UI 能正常打开并显示界面,说明 SkyWalking 部署成功。

17.7 连接信息

服务 地址 说明
OAP gRPC <节点IP>:11800 Agent 上报地址
OAP HTTP <节点IP>:12800 REST API / GraphQL
UI http://<节点IP>:18080 Web 界面
内部 OAP gRPC review-skywalking-oap:11800 overlay 网络内访问
内部 OAP HTTP review-skywalking-oap:12800 overlay 网络内访问

17.8 应用接入说明

业务应用需要集成 SkyWalking Agent 才能上报链路数据。

Java 应用接入示例

  1. 下载 SkyWalking Agent
wget https://archive.apache.org/dist/skywalking/java-agent/9.0.0/apache-skywalking-java-agent-9.0.0.tgz
tar -zxvf apache-skywalking-java-agent-9.0.0.tgz
  1. 启动应用时添加 Agent 参数:
java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \
     -Dskywalking.agent.service_name=your-service-name \
     -Dskywalking.collector.backend_service=<节点IP>:11800 \
     -jar your-app.jar

Docker 应用接入示例

services:
  my-service:
    image: my-app:latest
    environment:
      - JAVA_TOOL_OPTIONS=-javaagent:/skywalking-agent/skywalking-agent.jar
      - SW_AGENT_NAME=my-service
      - SW_AGENT_COLLECTOR_BACKEND_SERVICES=review-skywalking-oap:11800
    volumes:
      - /path/to/skywalking-agent:/skywalking-agent

17.9 常用运维命令

# 查看 OAP 日志
docker service logs review_skywalking_oap --tail 100

# 查看 UI 日志
docker service logs review_skywalking_ui --tail 100

# 进入 OAP 容器
docker exec -it $(docker ps -q -f name=review_skywalking_oap) bash

# 进入 UI 容器
docker exec -it $(docker ps -q -f name=review_skywalking_ui) bash

# 强制重启 OAP
docker service update --force review_skywalking_oap

# 强制重启 UI
docker service update --force review_skywalking_ui

# 删除并重新部署
docker stack rm review_skywalking
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking

17.10 常见问题及解决

17.10.1 OAP 启动失败:无法连接 Elasticsearch

问题: OAP 日志显示无法连接 ES

原因: ES 服务未启动或网络不通

解决:

# 检查 ES 是否正常运行
curl http://<ES节点IP>:9200

# 检查网络连通性(在 OAP 容器内)
docker exec $(docker ps -q -f name=review_skywalking_oap) curl review-es-elasticsearch:9200

# 确保 OAP 和 ES 在同一 overlay 网络
docker network inspect review

# 重启 OAP
docker service update --force review_skywalking_oap

17.10.2 UI 启动失败:无法连接 OAP

问题: UI 无法显示数据或无法访问

原因: OAP 尚未启动完成

解决:

# 确认 OAP 已启动
docker service logs review_skywalking_oap --tail 50 | grep -i "started"

# 重启 UI
docker service update --force review_skywalking_ui

17.10.3 OAP 内存不足 (OOM)

问题: OAP 进程被 OOM Killer 杀死

原因: JVM 内存设置过大或数据量过多

解决:

# 检查系统内存
free -h

# 修改 docker-compose.yml 中的 JAVA_OPTS
# JAVA_OPTS=-Xms1024m -Xmx1024m  # 根据实际情况调整

# 重新部署
docker stack rm review_skywalking
sleep 10
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_skywalking

17.10.4 ES 索引占用过多磁盘空间

问题: SkyWalking 索引数据增长过快

原因: 未配置索引生命周期策略

解决:

# 1. 创建索引生命周期策略(参考 17.3 节)

# 2. 手动删除旧索引
curl -X DELETE "http://<ES节点IP>:9200/sw_*_20241201*"

# 3. 查看 SkyWalking 索引
curl "http://<ES节点IP>:9200/_cat/indices?v" | grep sw_

17.10.5 Agent 无法连接 OAP

问题: 应用接入 Agent 后无数据上报

原因:

  • OAP 地址配置错误
  • 网络不通Agent 在容器外,需要用宿主机 IP
  • 端口未开放

解决:

# 1. 确认 Agent 配置的 OAP 地址
# 如果 Agent 在容器外,使用宿主机 IP:11800
# 如果 Agent 在同一 overlay 网络,使用 review-skywalking-oap:11800

# 2. 测试网络连通性
telnet <OAP地址> 11800

# 3. 检查 OAP gRPC 端口是否监听
netstat -tlnp | grep 11800

17.10.6 UI 显示无服务/无数据

问题: UI 可以访问但没有显示任何服务或数据

原因:

  • 尚未有应用接入 Agent
  • Agent 配置错误
  • 时间范围选择不对

解决:

# 1. 检查 OAP 是否收到数据
docker service logs review_skywalking_oap --tail 100 | grep -i "register"

# 2. 在 UI 中调整时间范围(右上角)

# 3. 确认应用已正确配置 Agent

十九、部署 MongoDB

18.1 服务说明

MongoDB 是一款开源的 NoSQL 文档数据库,本环境采用单机模式部署。

项目
Stack 名称 review_mongodb
镜像 bitnami/mongodb:6.0
端口 27017
Root 用户 root
Root 密码 123456
默认数据库 gps_data
部署节点 标签 review_mongodb=1 的节点

18.2 部署前准备

添加节点标签

# 将 MongoDB 部署到 ZD-BAK-APP2
docker node update --label-add review_mongodb=1 ZD-BAK-APP2

# 验证标签
docker node inspect ZD-BAK-APP2 --format '{{.Spec.Labels}}'

创建数据目录

# 在目标节点创建数据目录
mkdir -p /mnt/data/volumes/mongodb

# 设置权限Bitnami 镜像使用 UID 1001
chown -R 1001:1001 /mnt/data/volumes/mongodb

拉取镜像

方法一:直接拉取

docker pull bitnami/mongodb:6.0

方法二:通过私有仓库中转(推荐)

# 1. 在能访问外网的服务器(如 50 服务器)上操作
docker pull bitnami/mongodb:6.0
docker tag bitnami/mongodb:6.0 harbor.sino-assist.com/bitnami/mongodb:6.0
docker push harbor.sino-assist.com/bitnami/mongodb:6.0

# 2. 在目标服务器上从私有仓库拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/mongodb:6.0
docker tag harbor.sino-assist.com/bitnami/mongodb:6.0 bitnami/mongodb:6.0

18.3 配置文件说明

env_review 配置项

配置项 说明
NAMESPACE review 命名空间
NODE_PORT 27017 MongoDB 端口
MONGODB_DATABASE gps_data 默认创建的数据库

docker-compose.yml 关键配置

配置项 说明
MONGODB_ROOT_USER root 管理员用户名
MONGODB_ROOT_PASSWORD 123456 管理员密码
MONGODB_DATABASE ${MONGODB_DATABASE} 默认数据库(来自 env 文件)
volumes /mnt/data/volumes/mongodb:/bitnami/mongodb 数据持久化目录

18.4 部署命令

cd /opt/swarm/support/mongodb

# 转换 Windows 换行符
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb

18.5 验证部署

查看服务状态

# 查看服务列表
docker service ls | grep review_mongodb

# 预期输出:
# review_mongodb_db   replicated   1/1

# 查看详细状态
docker stack ps review_mongodb --no-trunc | grep -v Shutdown

测试连接

# 方法1通过容器内部测试
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "db.adminCommand('ping')"

# 预期输出:{ ok: 1 }

# 方法2查看数据库列表
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "show dbs"

验证挂载目录

# 查看容器挂载信息
docker inspect $(docker ps -q -f name=review_mongodb) --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

# 预期输出:/mnt/data/volumes/mongodb -> /bitnami/mongodb

# 检查目录是否有数据
ls -la /mnt/data/volumes/mongodb/

18.6 连接信息

项目
连接地址 mongodb://root:123456@<节点IP>:27017
内部地址 mongodb://root:123456@review-mongodb-db:27017
Root 用户 root
Root 密码 123456
默认数据库 gps_data

18.7 常用运维命令

# 查看服务日志
docker service logs review_mongodb_db --tail 100

# 进入 MongoDB 容器
docker exec -it $(docker ps -q -f name=review_mongodb_db) bash

# 进入 MongoDB Shell
docker exec -it $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456

# 强制重启服务
docker service update --force review_mongodb_db

# 删除并重新部署
docker stack rm review_mongodb
sleep 15
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb

18.8 MongoDB 常用命令

# 进入 MongoDB Shell 后执行

# 查看所有数据库
show dbs

# 切换数据库
use gps_data

# 查看当前数据库的集合
show collections

# 创建集合
db.createCollection("test_collection")

# 插入文档
db.test_collection.insertOne({ name: "test", value: 123 })

# 查询文档
db.test_collection.find()

# 创建用户
db.createUser({
  user: "app_user",
  pwd: "app_password",
  roles: [{ role: "readWrite", db: "gps_data" }]
})

# 查看用户
db.getUsers()

18.9 常见问题及解决

18.9.1 MongoDB 启动失败Permission denied

问题: MongoDB 无法写入数据目录

原因: 数据卷权限不正确Bitnami 镜像使用 UID 1001 运行

解决:

# 如果使用本地目录挂载,设置权限
chown -R 1001:1001 /path/to/mongodb/data

# 重启服务
docker service update --force review_mongodb_db

18.9.2 无法连接 MongoDB

问题: 应用无法连接 MongoDB

原因:

  • 网络不通
  • 认证信息错误
  • 端口未开放

解决:

# 1. 确认服务运行正常
docker service ls | grep review_mongodb

# 2. 确认端口监听(在 MongoDB 所在节点)
netstat -tlnp | grep 27017

# 3. 测试连接
docker exec $(docker ps -q -f name=review_mongodb_db) mongosh -u root -p 123456 --eval "db.adminCommand('ping')"

# 4. 检查网络(从其他容器)
docker run --rm --network review bitnami/mongodb:6.0 mongosh mongodb://root:123456@review-mongodb-db:27017 --eval "db.adminCommand('ping')"

18.9.3 认证失败

问题: Authentication failed

原因: 用户名或密码错误

解决:

# 确认连接字符串中的用户名和密码
# mongodb://root:123456@<地址>:27017

# 如果需要重置密码,删除数据卷重新部署
docker stack rm review_mongodb
sleep 10
docker volume rm review_mongodb_data_db
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_mongodb

18.9.4 磁盘空间不足

问题: MongoDB 写入失败,提示磁盘空间不足

原因: 数据卷所在磁盘空间不足

解决:

# 检查磁盘使用
df -h

# 检查 MongoDB 数据大小
docker exec $(docker ps -q -f name=review_mongodb_db) du -sh /bitnami/mongodb

# 清理不需要的数据或扩容磁盘

二十、部署 Redis Sentinel 集群(灾备从节点模式)

20.1 服务说明

本环境的 Redis 作为生产环境的灾备从节点部署,平时仅同步数据不提供服务,生产故障时可手动切换为独立集群。

项目
Stack 名称 review_redis
镜像 bitnami/redis:7.0.11, bitnami/redis-sentinel:7.0.11
Redis 端口 6379
Sentinel 端口 26379故障切换后启用
模式 隐藏从节点 + 级联复制(灾备模式)
生产主节点 192.168.10.56:6379
密码 sino#650
Sentinel 密码 sino#650
状态 已部署

20.2 架构说明

20.2.1 平时状态(数据同步,不提供服务)

┌─────────────────────────────────────────────────────────────────────────────┐
│                              生产环境                                        │
│                                                                             │
│    ┌──────────────┐        ┌──────────────┐                                │
│    │  Sentinel-1  │        │  Sentinel-2  │                                │
│    │ 10.55:26379  │        │ 10.56:26379  │                                │
│    └──────┬───────┘        └──────┬───────┘                                │
│           │        监控 mymaster   │                                        │
│           └────────────┬──────────┘                                        │
│                        ↓                                                   │
│    ┌──────────────┐        ┌──────────────┐                                │
│    │  10.55       │←───────│  10.56       │                                │
│    │  (Slave)     │  复制  │  (Master)    │                                │
│    └──────────────┘        └──────┬───────┘                                │
│                                   │                                        │
└───────────────────────────────────┼────────────────────────────────────────┘
                                    │
                                    │ 复制 (replica-announced no生产看不到)
                                    ↓
┌───────────────────────────────────────────────────────────────────────────────┐
│                              Review 环境(灾备)                               │
│                                                                               │
│    ┌──────────────────┐                                                       │
│    │  132 (master)    │  ← 从 10.56 同步,对生产 Sentinel 隐藏                  │
│    │  ZD-BAK-APP2     │                                                       │
│    │  隐藏 Slave      │                                                       │
│    └────────┬─────────┘                                                       │
│             │                                                                 │
│    ┌────────┴────────┐                                                        │
│    │ 级联复制        │                                                        │
│    ↓                 ↓                                                        │
│  ┌──────────────┐  ┌──────────────┐                                          │
│  │ 133 (slave-1)│  │ 134 (slave-2)│  ← 从 132 同步,可被 Review Sentinel 发现  │
│  │ zd-bak-app3  │  │ ZD-BAK-APP1  │                                          │
│  └──────────────┘  └──────────────┘                                          │
│                                                                               │
│  ⚠️ 平时无 Sentinel仅同步数据                                                │
└───────────────────────────────────────────────────────────────────────────────┘

20.2.2 故障切换后(独立 Sentinel 集群)

┌───────────────────────────────────────────────────────────────────────────────┐
│                              Review 环境(接管服务)                           │
│                                                                               │
│    ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                       │
│    │  Sentinel-1  │  │  Sentinel-2  │  │  Sentinel-3  │                       │
│    │ 132:26379    │  │ 133:26379    │  │ 134:26379    │                       │
│    └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                       │
│           │                 │                 │                               │
│           └─────────────────┼─────────────────┘                               │
│                             │ 监控 reviewmaster                               │
│                             ↓                                                 │
│    ┌──────────────────┐                                                       │
│    │  132 (Master)    │  ← SLAVEOF NO ONE 提升为主节点                         │
│    │  ZD-BAK-APP2     │                                                       │
│    └────────┬─────────┘                                                       │
│             │                                                                 │
│    ┌────────┴────────┐                                                        │
│    │       复制      │                                                        │
│    ↓                 ↓                                                        │
│  ┌──────────────┐  ┌──────────────┐                                          │
│  │ 133 (Slave)  │  │ 134 (Slave)  │                                          │
│  │ zd-bak-app3  │  │ ZD-BAK-APP1  │                                          │
│  └──────────────┘  └──────────────┘                                          │
│                                                                               │
│  ✅ 完整 Sentinel 集群,支持自动故障转移                                        │
└───────────────────────────────────────────────────────────────────────────────┘

20.2.3 关键配置说明

节点 replica-announced no replica-priority 0 说明
132 对生产 Sentinel 隐藏,不参与生产选举
133 可被 Review Sentinel 发现
134 可被 Review Sentinel 发现

为什么这样配置?

  • 132 需要隐藏132 直接从生产 10.56 复制,如果不隐藏,生产 Sentinel 会发现它并可能在故障时将其选为主节点
  • 133/134 不需要隐藏:它们从 132 复制,生产 Sentinel 本来就看不到;故障切换后需要被 Review Sentinel 发现

20.3 部署前准备

20.3.1 验证生产主节点可访问

# 1. 测试网络连通性
ping 192.168.10.56

# 2. 测试 Redis 端口是否开放
nc -zv 192.168.10.56 6379

# 3. 测试 Redis 连接和认证(如果服务器有 redis-cli
redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' PING
# 应返回 PONG

# 4. 检查 10.56 Redis 是否为主节点
redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' INFO replication
# 应显示 role:master

# 如果没有 redis-cli用 Docker 临时执行
docker run --rm bitnami/redis:7.0.11 redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' PING
docker run --rm bitnami/redis:7.0.11 redis-cli -h 192.168.10.56 -p 6379 -a 'sino#650' INFO replication

20.3.2 拉取镜像(所有节点)

由于服务分布在 3 个节点上,需要在每个节点都拉取镜像。

方法一:直接拉取(每个节点执行)

docker pull bitnami/redis:7.0.11
docker pull bitnami/redis-sentinel:7.0.11

方法二:通过私有仓库中转(推荐)

# 1. 在能访问外网的服务器上操作
docker pull bitnami/redis:7.0.11
docker pull bitnami/redis-sentinel:7.0.11
docker tag bitnami/redis:7.0.11 harbor.sino-assist.com/bitnami/redis:7.0.11
docker tag bitnami/redis-sentinel:7.0.11 harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11
docker push harbor.sino-assist.com/bitnami/redis:7.0.11
docker push harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11

# 2. 在每个目标节点上拉取
docker login harbor.sino-assist.com
docker pull harbor.sino-assist.com/bitnami/redis:7.0.11
docker pull harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11
docker tag harbor.sino-assist.com/bitnami/redis:7.0.11 bitnami/redis:7.0.11
docker tag harbor.sino-assist.com/bitnami/redis-sentinel:7.0.11 bitnami/redis-sentinel:7.0.11

20.4 配置文件说明

env_review 配置项

配置项 说明
NAMESPACE review 命名空间
REDIS_PASSWORD sino#650 Redis 密码(与生产相同)
REDIS_SENTINEL_PASSWORD sino#650 Sentinel 密码
REDIS_PROD_MASTER_HOST 192.168.10.56 生产主节点地址
REDIS_PROD_MASTER_PORT 6379 生产主节点端口
REDIS_REVIEW_PRIMARY_HOST 192.168.3.132 Review 主同步节点132

docker-compose.yml 关键配置

132 节点(主同步节点)配置:

配置项 说明
REDIS_REPLICATION_MODE slave 从节点模式
REDIS_MASTER_HOST ${REDIS_PROD_MASTER_HOST} 生产主节点地址 (10.56)
REDIS_EXTRA_FLAGS --replica-announced no --replica-priority 0 隐藏 + 不参与选举
ports mode host 使用主机网络端口

133/134 节点(级联从节点)配置:

配置项 说明
REDIS_REPLICATION_MODE slave 从节点模式
REDIS_MASTER_HOST ${REDIS_REVIEW_PRIMARY_HOST} Review 主节点 (132)
ports mode host 使用主机网络端口

REDIS_EXTRA_FLAGS 详解:

参数 作用
--replica-announced no 不向 Sentinel 通告自己,对 Sentinel 隐藏
--replica-priority 0 设置优先级为 0永不被选举为主节点

20.5 部署命令

cd /opt/swarm/support/redis-review-132

# 转换 Windows 换行符(如有需要)
sed -i 's/\r$//' docker-compose.yml
sed -i 's/\r$//' env_review

# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_redis

20.6 验证部署

20.6.1 查看服务状态

# 查看服务列表
docker service ls | grep review_redis

# 预期输出3 个服务):
# review_redis_master    replicated   1/1
# review_redis_slave-1   replicated   1/1
# review_redis_slave-2   replicated   1/1

# 查看详细状态
docker stack ps review_redis --no-trunc | grep -v Shutdown

20.6.2 验证同步状态

注意: 以下命令统一使用 Docker 方式执行,无需在服务器上安装 redis-cli。优先使用已运行的 review_redis_master 容器,如果容器未运行可使用临时容器方式。

方式一:使用已运行的容器(推荐)

# 检查 132 (从 10.56 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.10.56
# master_link_status:up

# 检查 133 (从 132 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.3.132
# master_link_status:up

# 检查 134 (从 132 同步)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication
# 预期输出:
# role:slave
# master_host:192.168.3.132
# master_link_status:up

方式二:使用临时容器(备选,容器未运行时使用)

# 定义 Redis 镜像
REDIS_IMAGE="bitnami/redis:7.0.11"

# 检查 132
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication

# 检查 133
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication

# 检查 134
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication

20.6.3 验证生产 Sentinel 看不到 Review 节点

# 在生产 Sentinel 上查看从节点列表(使用已运行容器)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.10.55 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL replicas mymaster

# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.10.55 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL replicas mymaster

# 预期:不应该看到 192.168.3.132、192.168.3.133、192.168.3.134

20.6.4 测试数据同步

# 在从节点查询数据(只读)- 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning KEYS '*' | head -10

# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning KEYS '*' | head -10

# 尝试写入(应该失败,从节点只读)
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SET test_key test_value
# 预期输出:(error) READONLY You can't write against a read only replica.

20.7 连接信息

平时状态(灾备同步):

节点 Redis 地址 角色
ZD-BAK-APP2 192.168.3.132:6379 隐藏从节点(从 10.56 同步)
zd-bak-app3 192.168.3.133:6379 从节点(从 132 同步)
ZD-BAK-APP1 192.168.3.134:6379 从节点(从 132 同步)
项目
Redis 密码 sino#650
生产主节点 192.168.10.56:6379

故障切换后:

项目
Review Master 192.168.3.132:6379
Review Sentinel 192.168.3.132:26379, 192.168.3.133:26379, 192.168.3.134:26379
Sentinel Master 名称 reviewmaster

20.8 故障切换

20.8.1 切换脚本

项目目录下提供了 failover-to-review.sh 脚本用于故障切换:

# 添加执行权限
chmod +x failover-to-review.sh

# 查看当前状态
./failover-to-review.sh status

# 完整故障切换(提升 132 为 Master + 部署 Sentinel
./failover-to-review.sh full

# 仅执行故障切换(不部署 Sentinel
./failover-to-review.sh failover

# 仅部署 Sentinel
./failover-to-review.sh sentinel

# 回滚到生产
./failover-to-review.sh rollback

20.8.2 手动切换步骤

如果不使用脚本,可按以下步骤手动操作:

注意: 以下命令优先使用已运行的 review_redis_master 容器执行,如果容器未运行可使用临时容器方式(备选命令)。

步骤 1提升 132 为 Master

# 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning SLAVEOF NO ONE

# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SLAVEOF NO ONE

步骤 2确认 133/134 指向 132

# 检查 133使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
# 如果不是 132重新配置
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379

# 检查 134使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
# 如果不是 132重新配置
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379

# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication | grep master_host
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379

步骤 3部署 Review Sentinel

cd /opt/swarm/support/redis-review-132
env $(cat ./env_review | xargs) envsubst < ./docker-compose-sentinel.yml | docker stack deploy --compose-file - review_sentinel

步骤 4验证 Sentinel

# 使用已运行容器
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.132 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL master reviewmaster

# 备选:使用临时容器
docker run --rm --network host bitnami/redis:7.0.11 redis-cli -h 192.168.3.132 -p 26379 -a 'sino#650' --no-auth-warning SENTINEL master reviewmaster

# 应显示 ip=192.168.3.132, port=6379, flags=master

步骤 5修改应用配置

# Spring Boot 配置
spring:
  redis:
    sentinel:
      master: reviewmaster
      nodes: 192.168.3.132:26379,192.168.3.133:26379,192.168.3.134:26379
      password: sino#650
    password: sino#650

20.8.3 回滚到生产

推荐使用安全回滚脚本: ./failover-to-review.sh rollback,该脚本会先将故障期间的数据同步到生产,确保数据不丢失。

使用已运行容器(推荐):

# 1. 移除 Review Sentinel
docker stack rm review_sentinel

# 2. 重新配置 132 指向生产
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning SLAVEOF 192.168.10.56 6379

# 3. 确认 133/134 指向 132
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379

# 4. 验证同步状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning INFO replication

备选:使用临时容器

REDIS_IMAGE="bitnami/redis:7.0.11"

# 1. 移除 Review Sentinel
docker stack rm review_sentinel

# 2. 重新配置 132 指向生产
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.10.56 6379

# 3. 确认 133/134 指向 132
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.134 -a 'sino#650' --no-auth-warning SLAVEOF 192.168.3.132 6379

# 4. 验证同步状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication

20.9 常用运维命令

使用已运行容器(推荐):

# 查看所有服务状态
docker stack ps review_redis

# 查看某个服务日志
docker service logs review_redis_master --tail 100
docker service logs review_redis_slave-1 --tail 100

# 在指定节点查看容器
docker ps | grep review_redis

# 进入 Redis CLI交互模式
docker exec -it $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning

# 查看复制状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -a 'sino#650' --no-auth-warning INFO replication

# 查看远程节点复制状态
docker exec $(docker ps -q -f name=review_redis_master) redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication

# 强制重启某个服务
docker service update --force review_redis_master

# 删除整个 Stack
docker stack rm review_redis

备选:使用临时容器(容器未运行时使用)

REDIS_IMAGE="bitnami/redis:7.0.11"

# 进入 Redis CLI交互模式
docker run --rm -it --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning

# 查看复制状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.132 -a 'sino#650' --no-auth-warning INFO replication

# 查看远程节点复制状态
docker run --rm --network host $REDIS_IMAGE redis-cli -h 192.168.3.133 -a 'sino#650' --no-auth-warning INFO replication

20.10 本次部署遇到的问题及解决方案总结

20.10.1 问题一Review Redis 节点被生产 Sentinel 发现

现象:

  • 将 Review Redis (132, 133, 134) 部署为生产 10.56 的从节点
  • 生产 Sentinel 发现了 Review 节点,并在故障时将 Review 节点选为主节点
  • 导致生产 Master 切换到 Review 环境

原因:

  • Redis 从节点默认会向 Sentinel 通告自己的存在
  • Sentinel 通过 INFO replication 命令自动发现所有从节点
  • 生产 Sentinel 将 Review 节点纳入了选举范围

解决方案:

# 使用 REDIS_EXTRA_FLAGS 配置隐藏
environment:
  - REDIS_EXTRA_FLAGS=--replica-announced no --replica-priority 0
参数 作用
--replica-announced no 不向 Sentinel 通告自己,对 Sentinel 隐藏
--replica-priority 0 设置优先级为 0即使被发现也不会被选举为主节点

注意: REDIS_REPLICA_PRIORITY=0 环境变量在 Bitnami 镜像中不生效,必须使用 REDIS_EXTRA_FLAGS

20.10.2 问题二Review Sentinel 无法正常监控

现象:

  • 部署 Review Sentinel 监控 132 作为 master
  • Sentinel 不断触发故障转移,将 master 从 132 切换到 133 或 134
  • 切换后又切回,形成循环

原因:

  • Sentinel 通过 INFO replication 检测节点角色
  • 132 因为从 10.56 复制role 永远是 slave
  • Sentinel 发现监控的 "master" 是 slave认为需要故障转移

解决方案:

采用"灾备模式"

  • 平时不部署 Review Sentinel仅同步数据
  • 生产故障时,先将 132 提升为真正的 MasterSLAVEOF NO ONE
  • 然后再部署 Review Sentinel

20.10.3 问题三env_review 文件中的注释导致部署失败

现象:

env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - review_redis

报错或环境变量为空

原因:

  • xargs 无法正确处理 # 开头的注释行
  • 导致环境变量解析错误

解决方案:

  • env_review 文件中不要使用注释
  • 或使用过滤:env $(cat ./env_review | grep -v '^#' | xargs)

20.10.4 问题四Sentinel 数据卷缓存旧配置

现象:

  • 修改 Sentinel 配置后重新部署
  • Sentinel 仍然使用旧的 master 配置

原因:

  • Sentinel 会将运行时配置写入数据卷
  • 重新部署时读取了旧的配置文件

解决方案:

# 删除 Sentinel 数据卷后重新部署
docker volume rm review_redis_sentinel_data_1
docker volume rm review_redis_sentinel_data_2
docker volume rm review_redis_sentinel_data_3

20.10.5 问题五133/134 配置 replica-announced no 导致故障切换后 Sentinel 发现不了

现象:

  • 所有节点都配置了 replica-announced no
  • 故障切换后Review Sentinel 无法发现 133/134 作为从节点

原因:

  • replica-announced no 对所有 Sentinel 生效,不区分生产和 Review
  • 133/134 从 132 复制,生产 Sentinel 本来就看不到它们

解决方案:

  • 只有 132 需要配置 replica-announced no(因为它直接从生产复制)
  • 133/134 不需要配置,因为生产 Sentinel 通过 132 也发现不了它们

20.11 架构设计总结

设计目标 实现方式
Review 同步生产数据 132 从 10.56 复制133/134 从 132 级联复制
对生产 Sentinel 隐藏 132 配置 replica-announced no
不影响生产选举 132 配置 replica-priority 0
故障时可独立运行 提供切换脚本和独立 Sentinel 配置
切换后支持高可用 部署 Review Sentinel 监控 reviewmaster
可回滚到生产 提供 rollback 命令重新指向 10.56

20.12 文件清单

文件 说明
docker-compose.yml Review Redis 配置(平时使用)
docker-compose-sentinel.yml Review Sentinel 配置(故障切换后部署)
env_review 环境变量
failover-to-review.sh 故障切换脚本

二十一、服务部署命令汇总

20.1 通用部署模式

cd /path/to/service

# 使用环境变量文件部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose.yml | docker stack deploy --compose-file - <stack名称>

19.2 常用管理命令

# 查看所有 Stack
docker stack ls

# 查看 Stack 中的服务
docker stack ps <stack名称>

# 查看服务日志
docker service logs <服务名>

# 删除 Stack
docker stack rm <stack名称>

# 更新服务
docker service update --force <服务名>

# 查看所有服务
docker service ls

# 查看镜像
docker images | grep <关键字>

# 查看 Docker Config
docker config ls

二十二、待部署服务清单

无依赖服务

服务 端口 状态 说明
Redis Sentinel 6379, 26379 已部署 缓存服务(从 10.56 同步)
MongoDB 27017 已部署 NoSQL 数据库
RabbitMQ 5672, 15672 已部署 消息队列
Elasticsearch 9200, 9300 已部署 搜索引擎
MinIO 9000 待部署 对象存储
Jenkins 8081 待部署 CI/CD
Monitor 3000 待部署 Grafana + Prometheus
ClickHouse 8123 待部署 OLAP 分析数据库

有依赖服务

服务 依赖 状态 说明
MySQL 主从 (mysql-repl-tool) 已部署 工具类服务专用数据库
Nacos MySQL 已部署 服务注册/配置中心
XXL-Job-Admin MySQL 已部署 分布式任务调度
Kibana Elasticsearch 已部署 ES 可视化
SkyWalking Elasticsearch 已部署 APM 链路追踪
Log (Logstash + Filebeat) Elasticsearch 已部署 日志处理
Canal MySQL, RabbitMQ 已部署 数据同步

二十三、服务依赖关系图

                    ┌─────────┐
                    │ Network │  ← 必须最先创建
                    └────┬────┘
                         │
                    ┌────┴────┐
                    │Portainer│  ← 管理入口
                    └────┬────┘
                         │
         ┌───────────────┼───────────────┐
         ▼               ▼               ▼
    ┌─────────┐    ┌─────────┐    ┌─────────┐
    │  MySQL  │    │  Redis  │    │   ES    │
    └────┬────┘    └─────────┘    └────┬────┘
         │                              │
    ┌────┴────────────┐           ┌────┴────┐
    │                 │           │         │
    ▼                 ▼           ▼         ▼
┌───────┐      ┌──────────┐  ┌───────┐ ┌──────────┐
│ Nacos │      │XXL-Job   │  │Kibana │ │SkyWalking│
└───┬───┘      │Admin     │  └───────┘ └──────────┘
    │          └──────────┘
    ▼
┌─────────────────┐
│ 业务微服务       │
│ (注册到 Nacos)   │
└─────────────────┘

附录:目录结构

docker-swarm-review/
├── 10.5x环境配置记录/    # 环境部署文档
├── portainer/           # Portainer 管理平台
├── monitor/             # Grafana + Prometheus
├── mysql/               # MySQL 单机
├── mysql-repl-tool/     # MySQL 主从复制
├── redis/               # Redis 主从 + Sentinel
├── redis-review-132/    # Review 环境 Redis
├── mongodb/             # MongoDB
├── rabbitmq/            # RabbitMQ
├── elasticsearch/       # Elasticsearch
├── nacos/               # Nacos 单机
├── nacos-cluser/        # Nacos 集群
├── xxl-job-admin/       # XXL-Job
├── jenkins/             # Jenkins
├── canal/               # Canal
├── clickhouse/          # ClickHouse
├── minio/               # MinIO
├── nginx/               # Nginx
├── nginx-review-132/    # Review 环境 Nginx
├── skywalking/          # SkyWalking
├── log/                 # ELK 日志
└── datart/              # BI 数据可视化

二十四、基础镜像详解

22.1 什么是基础镜像

基础镜像是构建应用镜像的"地基",包含运行应用所需的操作系统、运行时环境和通用工具。

基础镜像地址: harbor.sino-assist.com/marsal1212/java11:latest

22.2 基础镜像结构

harbor.sino-assist.com/marsal1212/java11:latest
│
├── FROM openjdk:11-jdk              # 官方 OpenJDK 11 镜像Debian 系统)
│
├── 系统工具
│   ├── curl        # HTTP 请求工具(健康检查用)
│   ├── wget        # 下载工具
│   ├── procps      # 进程管理工具ps、top
│   ├── lsof        # 查看打开文件
│   ├── iotop       # IO 监控
│   └── lib32gcc-s1 # 32位兼容库
│
└── /skywalking-agent/               # SkyWalking Agent链路追踪
    ├── skywalking-agent.jar         # Agent 主程序
    ├── config/
    │   └── agent.config             # 配置文件
    ├── plugins/                     # 默认插件(自动加载)
    ├── optional-plugins/            # 可选插件
    ├── activations/                 # 工具包激活器
    └── bootstrap-plugins/           # 引导插件

22.3 Dockerfile 解析

FROM --platform=linux/amd64 openjdk:11-jdk
# 基于官方 OpenJDK 11 镜像,强制使用 amd64 架构(兼容性)

RUN apt-get update && apt-get install -y curl iotop wget procps lsof lib32gcc-s1
# 安装运维/调试常用工具:
#   - curl: 用于容器内健康检查curl localhost:8080/actuator/health
#   - procps: 提供 ps、top 命令,排查问题时查看进程
#   - lsof: 查看端口占用、文件句柄
#   - iotop: IO 性能排查

ADD skywalking-agent /skywalking-agent
# 将本地 skywalking-agent 目录复制到镜像的 /skywalking-agent 路径
# 这样所有基于此镜像的应用都可以直接使用 Agent

CMD ["java","--version"]
# 默认命令(会被应用镜像覆盖)

22.4 镜像层级关系

┌─────────────────────────────────────────────────────────┐
│  应用镜像 (harbor.sino-assist.com/sa-server/sa-gateway) │  ← Jib 构建
│  - 应用 JAR 包                                          │
│  - 应用配置                                             │
├─────────────────────────────────────────────────────────┤
│  基础镜像 (harbor.sino-assist.com/marsal1212/java11)    │  ← 自定义镜像
│  - SkyWalking Agent                                     │
│  - curl/wget/procps 等工具                              │
├─────────────────────────────────────────────────────────┤
│  openjdk:11-jdk                                         │  ← 官方镜像
│  - JDK 11 运行时                                        │
│  - Debian 操作系统                                      │
└─────────────────────────────────────────────────────────┘

22.5 基础镜像如何被使用

Jib 构建时引用

sa-server 项目的 build.gradle 中配置:

jib {
    from {
        image = "harbor.sino-assist.com/marsal1212/java11:latest"  // 基础镜像
    }
    to {
        image = "${docker_hub}/sa-server/${project.name}"          // 目标镜像
        tags = ["${docker_version}"]
    }
}

构建流程

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  源代码编译      │ ──► │  Jib 打包       │ ──► │  推送到 Harbor  │
│  (Gradle)       │     │  (基于基础镜像)  │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Jib 做的事情:
1. 拉取基础镜像 (java11:latest)
2. 在其之上添加应用层JAR、配置
3. 设置启动命令
4. 推送到 Harbor

最终应用镜像内容

harbor.sino-assist.com/sa-server/sa-gateway:master
│
├── /skywalking-agent/           # 来自基础镜像
│   └── skywalking-agent.jar
│
├── /app/                        # Jib 添加的应用层
│   ├── classes/                 # 编译后的类文件
│   ├── libs/                    # 依赖 JAR
│   └── resources/               # 配置文件
│
└── ENTRYPOINT: java -cp /app/... MainClass

22.6 运行时如何启用 SkyWalking

配置位置说明

重要: SkyWalking 相关配置已在 build.gradle 的 Jib entrypoint 中设置,无需在 pipeline-script 中重复配置。

// build.gradle 中的 Jib entrypoint 配置
entrypoint = ["/bin/sh", "-c",
    'java -javaagent:/skywalking-agent/skywalking-agent.jar ' +
    '-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=${namespace}-skywalking-oap:11800 ' +
    '-DSW_AGENT_NAME=$project_name ' +
    '-DSW_AGENT_INSTANCE_NAME=$project_name:${nativeIp} ' +
    ...
]

容器启动流程

1. Docker 启动容器
   │
2. 执行 Jib 配置的 entrypoint包含 -javaagent 参数)
   │  命令: java -javaagent:/skywalking-agent/skywalking-agent.jar ...
   │
3. SkyWalking Agent 加载
   │
4. Agent 读取系统属性(-D 参数)
   │  - -DSW_AGENT_NAME=$project_name → 服务名
   │  - -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=${namespace}-skywalking-oap:11800 → OAP 地址
   │
5. Agent 开始采集链路数据,上报到 OAP

关键环境变量(由 pipeline-script 设置,被 entrypoint 引用)

环境变量 作用 示例值
namespace 用于构建 OAP 地址 ${namespace}-skywalking-oap:11800 review
project_name 服务名SkyWalking UI 显示) sa-gateway
nativeIp 实例名后缀 192.168.3.132

22.7 基础镜像的优势

优势 说明
统一管理 所有服务使用相同的 JDK 版本和工具
减少重复 SkyWalking Agent 只需打包一次
快速构建 Jib 只需添加应用层,不用重复下载基础层
便于升级 更新基础镜像后,重新构建应用即可生效
运维友好 内置 curl/lsof 等工具,方便排查问题

22.8 如何更新基础镜像

如果需要更新 SkyWalking Agent 版本或添加工具:

# 1. 修改 Dockerfile 或更新 skywalking-agent 目录

# 2. 重新构建基础镜像
cd builder-docker/java11
docker build -f Dockerfile -t harbor.sino-assist.com/marsal1212/java11:latest .

# 3. 推送到 Harbor
docker push harbor.sino-assist.com/marsal1212/java11:latest

# 4. 重新构建所有应用镜像Jenkins 中重新打包)

二十五、Jenkins Pipeline 部署脚本

23.1 脚本概述

Jenkins Pipeline 脚本用于自动化构建和部署微服务应用到 Docker Swarm 集群。

脚本位置: docker-swarm-review/pipeline-script

备份文件: docker-swarm-review/jenkins/pipeline-script-backup-with-comments

23.2 脚本功能

阶段 功能
checkout 从 GitLab 拉取代码
docker-build-push 使用 Gradle Jib 构建镜像并推送到 Harbor
docker-deploy 通过 SSH 远程部署到 Docker Swarm

23.3 Review 环境配置

配置项 说明
harbor harbor.sino-assist.com 镜像仓库地址
deploy_server 192.168.3.132 部署服务器Manager 节点)
profile review Spring 环境
nacos_address review-nacos1:8848,review-nacos2:8848,review-nacos3:8848 Nacos 集群地址
namespace review 命名空间(同时用于构建 SkyWalking OAP 地址)
SSH 用户 root SSH 登录用户
网络子网 10.18.0.0/16 Docker Overlay 网络

23.4 节点映射表

IP 主机名 角色
192.168.3.132 ZD-BAK-APP2 Manager (Leader)
192.168.3.133 zd-bak-app3 Worker
192.168.3.134 ZD-BAK-APP1 Worker

23.5 完整脚本(含详细注释)

#!/usr/bin/env groovy
// ============================================================================
// Jenkins Pipeline 脚本Review 环境)
// 更新时间2025-12-23
// ============================================================================

import groovy.json.JsonSlurperClassic
// 导入 Groovy JSON 解析类,用于解析 Jenkins 参数传入的 JSON 配置

//properties(projectProperties)
def jsonOption = new JsonSlurperClassic().parseText(params.modulesOption)
// 解析 Jenkins 参数 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 格式示例

{
  "deploy_modules": [
    {
      "module": "sa-gateway",
      "o": true,
      "node": 1,
      "cpu": "0.5-2",
      "memory": "512M-2G",
      "address": "192.168.3.132:18080"
    }
  ],
  "deploy_step": "打包镜像,部署服务"
}

二十四、前端服务部署 (sa-cc)

24.1 概述

前端服务 sa-cc 是 Vue 项目,部署采用 Docker Swarm Stack 方式。

24.2 Jenkins Pipeline 脚本

脚本文件:docker-swarm-review/pipeline-script-cc

配置说明

配置项 Review 值 说明
deploy_server 192.168.3.132 部署目标服务器
profile review 环境标识
NAMESPACE review Docker 网络命名空间
REGISTRY_URL harbor.sino-assist.com 镜像仓库地址
SSH 用户 root 远程执行用户
SSH 密码 Sino.2025 远程执行密码

脚本内容

#!/usr/bin/env groovy

def projectProperties = [
        [$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '5']],
        parameters([
                string(name: 'branch', description: '分支'),
                booleanParam(name: 'yarnInstall', description: '是否更新node_modules'),
                string(name: 'backUrl', defaultValue: 'https://api1.sino-assist.com', description: 'backend server url')
        ])
]

def deploy_server = "192.168.3.132"
def profile = "review"
def NAMESPACE = "review"
def REGISTRY_URL = "harbor.sino-assist.com"

node {
    def workspace = pwd()
    def DOCKER_CREDENTIAL_ID = 'harbor'

    stage('checkout') {
        git branch: branch, credentialsId: 'gitlab', url: 'https://git.sino-assist.com/server/sa-cc.git'
    }

    if(params.build==true){

         stage('build') {
                nodejs(nodeJSInstallationName: 'nodejs-v22') {
                    if(params.yarnInstall == true){
                        sh "yarn"
                    }
                    sh "export NODE_OPTIONS=--openssl-legacy-provider"
                    String backUrl = params.backUrl
                    if(profile == 'prod'){
                        sh "sed -i 's|VUE_APP_BACK_REST_URL_PLACE_HOLDER|${backUrl}|' ${workspace}/.env.prod"
                        sh "yarn build-prod"
                    }else{
                        sh "sed -i 's|VUE_APP_BACK_REST_URL_PLACE_HOLDER|${backUrl}|' ${workspace}/.env.alpha"
                        sh "yarn build"
                    }
                }

        }

        stage('docker-login') {
          withCredentials([usernamePassword(passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME', credentialsId: "${DOCKER_CREDENTIAL_ID}",)]) {
                sh "echo '$DOCKER_PASSWORD' | docker login ${REGISTRY_URL} -u '$DOCKER_USERNAME' --password-stdin"
          }
        }

        stage('docker-build') {
            sh " docker build -f k8s/Dockerfile -t ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE} ."
        }

        stage('docker-push') {
            sh "docker push ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE}"
        }
    }

    stage('deploy') {
         String yml = """
version: \\"3.8\\"
services:
    svc:
        image: ${REGISTRY_URL}/new-sino/sa-cc:${NAMESPACE}
        ports:
            - '8081:8080'
        environment:
            - TZ=Asia/Shanghai
        deploy:
            mode: replicated
            replicas: 1
            restart_policy:
                condition: on-failure
                delay: 5s
                max_attempts: 3
            update_config:
                order: start-first

            resources:
                limits:
                    cpus: \\"1\\"
                    memory: "300M"
                reservations:
                    cpus: \\"4\\"
                    memory: "4G"
            placement:
                constraints:
                  - "node.labels.${NAMESPACE}_sa-cc==1"
networks:
    default:
        name: ${NAMESPACE}
        external: true
"""
        String serverName = "${NAMESPACE}_ss_sa-cc"
        String ymlFile = "/data/swarm/${serverName}.yml"
        String deploy = "sshpass -p 'Sino.2025' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@${deploy_server} \" mkdir -p /data/swarm/ && echo '''${yml}''' > ${ymlFile} && docker stack deploy -c ${ymlFile} ${serverName} --prune --with-registry-auth\""
        echo deploy
        sh deploy
    }
}

24.3 部署前准备

添加节点标签

前端服务通过节点标签约束部署位置,需要先在目标节点添加标签:

# 查看集群节点
docker node ls

# 在目标节点添加标签(示例:部署到 132 节点)
docker node update --label-add review_sa-cc=1 ZD-BAK-APP2

24.4 Jenkins 配置

参数化构建配置

参数名 类型 说明
branch String Git 分支名
build Boolean 是否执行构建(可只部署不构建)
yarnInstall Boolean 是否更新 node_modules
backUrl String 后端 API 地址

24.5 访问验证

部署完成后,前端服务监听 8081 端口:

http://192.168.3.132:8081

二十五、Nginx 反向代理(解决跨域)

25.1 问题背景

直接通过 IP:端口 访问前端时,前端 (8081) 调用后端 (28092) 会因端口不同触发浏览器跨域限制:

Access to XMLHttpRequest at 'http://192.168.3.132:28092/common/auth/token'
from origin 'http://192.168.3.132:8081' has been blocked by CORS policy

同源策略:协议 + 域名/IP + 端口 三者必须完全相同,端口不同即为跨域。

25.2 解决方案

部署 Nginx 反向代理,将前后端统一到同一端口:

http://192.168.3.132:8080
    ├── /              → 前端 (8081)
    └── /api路径/      → 后端网关 (28092)

25.3 部署步骤

25.3.1 创建配置目录

mkdir -p /data/nginx-proxy

25.3.2 创建 Nginx 配置文件

cat > /data/nginx-proxy/nginx.conf << 'EOF'
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;
    client_max_body_size 100M;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent"';

    access_log /var/log/nginx/access.log main;
    error_log  /var/log/nginx/error.log warn;

    # 18092接收来自 3.110 nginx 的 apireview 转发请求,记录日志后再转发到网关
    server {
        listen 18092;

        access_log /var/log/nginx/access.log main;

        location / {
            proxy_pass http://192.168.3.132:28092;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen 8080;

        # 前端
        location / {
            proxy_pass http://192.168.3.132:8081/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        # 后端网关 - 所有 API 请求
        location ~ ^/(common|order|supplier|contract|base|export-app|auth|user|system|api|ws|return|returnVehicle|returnOrder|supplierManage|agg-api|zgs|gps|data-search)/ {
            proxy_pass http://192.168.3.132:28092;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}
EOF

25.3.3 启动 Nginx 容器

mkdir -p /data/nginx-proxy/logs

docker run -d \
  --name nginx-proxy \
  --restart always \
  -p 8080:8080 \
  -p 18092:18092 \
  -v /data/nginx-proxy/nginx.conf:/etc/nginx/nginx.conf:ro \
  -v /data/nginx-proxy/logs:/var/log/nginx \
  nginx:alpine

25.3.4 配置日志按天分割logrotate

日志写入宿主机 /data/nginx-proxy/logs/,由 logrotate 每天切割,保留 90 天约3个月

cat > /etc/logrotate.d/nginx-proxy << 'EOF'
/data/nginx-proxy/logs/*.log {
    daily
    rotate 90
    dateext
    dateformat -%Y%m%d
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        docker kill --signal=USR1 nginx-proxy 2>/dev/null || true
    endscript
}
EOF

验证配置并手动执行一次:

# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-proxy

# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-proxy

logrotate 默认由系统 cron 每天凌晨自动执行(/etc/cron.daily/logrotate),无需额外配置定时任务。

25.3.5 nginx-review-132 日志按天分割logrotate

nginx-review-132 通过 Docker Swarm 部署在 ZD-BAK-APP2 节点上,日志挂载在宿主机 /opt/logs/nginx/,在该节点上执行以下配置。

cat > /etc/logrotate.d/nginx-review << 'EOF'
/opt/logs/nginx/*.log {
    daily
    rotate 90
    dateext
    dateformat -%Y%m%d
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        docker kill --signal=USR1 $(docker ps -qf name=nginx-review) 2>/dev/null || true
    endscript
}
EOF

验证配置并手动执行一次:

# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-review

# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-review

25.3.6 生产环境 nginx 日志按天分割logrotate

生产 nginxprod_nginx_server1)部署在 ZD-CRM1日志挂载在宿主机 /opt/logs/nginx/。需在 ZD-CRM1 和 ZD-CRM2 两台节点上分别执行prod_nginx_server2 后续恢复后同样生效)。

注意:/opt/logs/ 目录权限为 777需在配置中加 su root sasys 指令,否则 logrotate 会因权限问题跳过。

cat > /etc/logrotate.d/nginx-prod << 'EOF'
/opt/logs/nginx/*.log {
    su root sasys
    daily
    rotate 90
    dateext
    dateformat -%Y%m%d
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        docker kill --signal=USR1 $(docker ps -qf name=prod_nginx) 2>/dev/null || true
    endscript
}
EOF

验证配置并手动执行一次:

# 验证配置语法
logrotate -d /etc/logrotate.d/nginx-prod

# 手动执行一次(不影响生产)
logrotate -f /etc/logrotate.d/nginx-prod

25.3.7 常见问题排查

问题一logrotate 显示已执行但无归档文件生成

现象:/var/lib/logrotate/logrotate.status 中有记录,但 /opt/logs/nginx/ 目录下没有 .gz 归档文件。

原因logrotate 首次执行时日志文件因长期权限问题积压过大,压缩超时或 IO 中断导致归档失败,但 status 已写入,后续每天判断"今天已转过"而跳过。

排查命令:

# 查看 status 记录
cat /var/lib/logrotate/logrotate.status | grep nginx

# 查看 cron 执行日志
grep logrotate /var/log/cron | tail -10

问题二logrotate 执行后 nginx 未切换到新日志文件(仍写入 access.log-old

现象:发送 USR1 信号后,新的 access.log 没有日志写入nginx 仍在写旧文件。

原因nginx 容器内进程以 uid 1001 运行,tail -c 生成的临时文件默认属主为 root1001 无写入权限,USR1 后重新 open 失败,继续持有旧文件描述符。

解决:

# 修正新 access.log 的权限
chmod 777 /opt/logs/nginx/access.log

# 重新发送 USR1
docker kill --signal=USR1 $(docker ps -qf name=prod_nginx)

# 确认已切换
tail -f /opt/logs/nginx/access.log

问题三:日志文件积压过大(数百 GB需手动清理后重新切割

适用场景logrotate 长期未生效导致 access.log 积压,需保留最近部分数据后重新切割。

# 1. 截取最后 10G 到临时文件(保留近期数据)
tail -c 10G /opt/logs/nginx/access.log > /opt/logs/nginx/access.log.tmp

# 2. 原子替换nginx 此时仍写旧文件,不中断)
mv /opt/logs/nginx/access.log /opt/logs/nginx/access.log-old
mv /opt/logs/nginx/access.log.tmp /opt/logs/nginx/access.log

# 3. 修正权限nginx 以 1001 运行,需可写)
chmod 777 /opt/logs/nginx/access.log

# 4. 发送 USR1让 nginx 重新打开文件
docker kill --signal=USR1 $(docker ps -qf name=prod_nginx)

# 5. 确认 nginx 已切换到新文件
tail -f /opt/logs/nginx/access.log

# 6. 确认后删除旧的积压文件
rm /opt/logs/nginx/access.log-old

# 7. 强制执行 logrotate重新建立切割基准
logrotate -f /etc/logrotate.d/nginx-prod

注意:mv 是原子操作nginx 在收到 USR1 之前始终持有旧文件的文件描述符,文件被 mv 走不会中断写入,USR1 之后才重新 open 新的 access.log

25.3.8 将 3.110 宿主机 nginx 迁移到 3.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 上创建目录并上传文件

mkdir -p /data/nginx/sites
mkdir -p /data/nginx/ssl/23368363_sino-assist.com_nginx
mkdir -p /data/nginx/ssl/2026
mkdir -p /opt/logs/nginx

# 上传文件:
# nginx.conf、ssl.sino_assist.conf、ssl.conf → /data/nginx/
# sites/*.conf → /data/nginx/sites/
# 证书文件 → /data/nginx/ssl/ 对应目录
# docker-compose.yml、logrotate-nginx → /data/nginx/

2. 部署 Swarm service在 manager 节点执行)

docker stack deploy -c /data/nginx/docker-compose.yml nginx-review

验证:

docker service ls | grep nginx-review
docker service ps nginx-review_nginx-review

3. 验证各域名转发正常

# review 接口(返回 200 或 4xx 均表示 nginx 转发正常)
curl -k -o /dev/null -s -w "%{http_code}" --resolve "apireview.sino-assist.com:443:192.168.3.132" https://apireview.sino-assist.com/common/auth/token

# review 前端
curl -k -o /dev/null -s -w "%{http_code}" --resolve "ccreview.sino-assist.com:443:192.168.3.132" https://ccreview.sino-assist.com/

4. 配置 logrotate在 ZD-BAK-APP2 上执行)

cp /data/nginx/logrotate-nginx /etc/logrotate.d/nginx-review
logrotate -d /etc/logrotate.d/nginx-review   # 验证
logrotate -f /etc/logrotate.d/nginx-review   # 手动执行一次建立基准

注意ZD-BAK-APP2 上无 sasys 组,/opt/logs/nginx/ 属主为 rootlogrotate 配置中不需要 su root sasys 指令。

5. DNS 切流

联系 DNS 负责人,将所有域名 A 记录从 192.168.3.110 改为 192.168.3.132

建议分批切流:

  1. Review 环境(apireviewccreview
  2. 内网工具(jirawikiharborgit 等)
  3. 生产核心(crm1api2crm2uat

6. 停止 3.132 旧的 nginx-proxy 容器

DNS 切流并确认所有域名访问正常后执行:

docker stop nginx-proxy && docker rm nginx-proxy

7. 迁移验证

DNS 切流前,可通过本地 hosts 绑定提前验证WindowsC:\Windows\System32\drivers\etc\hosts

192.168.3.132  apireview.sino-assist.com
192.168.3.132  ccreview.sino-assist.com
192.168.3.132  crm1.sino-assist.com
192.168.3.132  api2.sino-assist.com
192.168.3.132  crm2.sino-assist.com
192.168.3.132  uat.sino-assist.com
# 其他需要验证的域名...

验证地址清单:

域名 测试地址 期望结果
Review 接口 https://apireview.sino-assist.com/common/auth/token 200
Review 前端 https://ccreview.sino-assist.com/ 200
CRM1 https://crm1.sino-assist.com/ 200
CRM2 接口 https://api2.sino-assist.com/common/auth/token 200
CRM2 前端 https://crm2.sino-assist.com/ 200/301
UAT https://uat.sino-assist.com/common/auth/token 200
Portainer https://portainer.sino-assist.com/ 200
Git https://git.sino-assist.com/ 200
Harbor https://harbor.sino-assist.com/ 200
Report https://report.sino-assist.com/ 200

注意:根路径 / 返回 404 属正常现象Spring Boot 网关无根路由),需测试实际接口路径如 /common/auth/token

验证完成后删除 hosts 中的临时绑定记录。

DNS 切流后,在 3.132 上确认日志有请求写入,同时确认 3.110 上 nginx 日志停止增长:

# 3.132 上
tail -f /opt/logs/nginx/access.log

# 3.110 上
tail -f /zd/src/nginx/logs/access.log

25.4 访问验证

部署完成后,通过统一入口访问:

http://192.168.3.132:8080

25.5 配置更新

如需添加新的后端 API 路径,修改 /data/nginx-proxy/nginx.conf 中的正则表达式:

location ~ ^/(common|order|新路径1|新路径2)/ {

然后重启容器:

docker restart nginx-proxy

25.6 当前支持的 API 路径

路径前缀 说明
common 通用服务
order 订单服务
supplier 供应商服务
contract 合同服务
base 基础服务
export-app 导出服务
auth 认证服务
user 用户服务
system 系统服务
api 通用 API
ws WebSocket
return 返程服务
returnVehicle 返程车辆
returnOrder 返程订单
supplierManage 供应商管理
agg-api 聚合 API
zgs 中钢服务
gps GPS 服务
data-search 数据搜索

25.7 常见问题

问题 1504 Gateway Timeout

后端响应超时,可在 location 块中添加:

proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

问题 2WebSocket 连接失败

确保已配置 Upgrade 头:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

问题 3新增 API 路径 404

检查 location 正则是否包含该路径,更新配置后重启容器。


25.9 nginx-review-132 变更记录

25.9.1 新增 oss.sinoassist.com 路由2026-04-29

新增 sites/oss.conf,将 oss.sinoassist.com 的请求直接转发到 3.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 上创建文件后热重载生效,无需重新部署:

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.2Swarm ingress IPVS 地址),时间为 UTC 无 +8。

原因: 端口以默认 ingress 模式发布时,流量经过 Swarm ingress 网络的 IPVS 转发,真实源 IP 在到达容器前已被替换,无法通过 log_formatX-Forwarded-For 恢复。

解决:

  1. 端口发布模式改为 mode: host,绕过 ingress 网络nginx 直接收到真实客户端 IP
  2. 新增 TZ=Asia/Shanghai 环境变量修正时区

docker-compose.yml 关键变更:

services:
  nginx-review:
    environment:
      - TZ=Asia/Shanghai
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
      # 其他端口同样改为 mode: host

注意:mode: hostdocker service ls 不显示端口映射,属正常现象,实际端口仍正常监听。

重新部署生效(会有秒级中断):

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。

排查过程:

# 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

location / {
    proxy_pass http://192.168.1.209:8080/;
    proxy_set_header Host $host;               # 补充此行
    proxy_set_header X-Forwarded-Host $server_name;
    ...
}

生效方式(无中断):

# 将修改后的 crm1.conf、crm2.conf 上传到 /data/nginx/sites/ 后执行
docker exec $(docker ps --filter name=nginx-review -q) nginx -t
docker exec $(docker ps --filter name=nginx-review -q) nginx -s reload

注意:凡是 location 块内有自定义 proxy_set_header 的,必须同时显式声明 proxy_set_header Host $host,否则全局设置会被静默覆盖。

25.9.4 修复 uat 域名访问 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

生效方式(无中断):

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 分别监听 18888HTTP18889HTTPS外部无法通过同一端口区分协议。

方案:nginx.conf 追加 stream {} 块,利用 ssl_preread 在 TCP 层识别协议,将 8888/38888 的流量自动路由到 1888818889

stream {
    upstream http_gateway {
        server 127.0.0.1:18888;
    }
    upstream https_gateway {
        server 127.0.0.1:18889;
    }
    map $ssl_preread_protocol $upstream {
        default     http_gateway;
        "TLSv1.0"   https_gateway;
        "TLSv1.1"   https_gateway;
        "TLSv1.2"   https_gateway;
        "TLSv1.3"   https_gateway;
    }
    server {
        listen 8888;
        listen 38888;
        ssl_preread on;
        proxy_pass $upstream;
    }
}

流量路径:

客户端 → 8888 或 38888
    ├── HTTP  → 127.0.0.1:18888 → fastdfs.conf server (listen 18888)
    └── HTTPS → 127.0.0.1:18889 → fastdfs.conf server (listen 18889 ssl)

同步在 docker-compose.yml 新增 38888 端口映射(mode: host)。

生效方式(有秒级中断):

cd /opt/swarm/support/nginx-review-132
docker stack deploy --compose-file docker-compose.yml nginx-review

验证:

# 验证 8888 HTTP 转发
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.132:8888/

# 验证 38888
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.132:38888/

# 验证 nginx 转发到后端 3.119:8888
curl -o /dev/null -s -w "%{http_code}" http://192.168.3.119:8888/

二十六、部署进度更新

26.1 已完成服务

阶段 状态
系统准备 完成
数据盘挂载 完成
Docker 安装 完成
Swarm 初始化 完成
Overlay 网络 完成
Portainer 完成
MySQL 主从复制 (mysql-repl-tool) 完成
RabbitMQ 集群 完成
Nacos 集群 完成
XXL-Job-Admin 完成
Canal 完成
Elasticsearch + Kibana 完成
Log (Logstash + Filebeat) 完成
SkyWalking 完成
MongoDB 完成
Redis Sentinel 集群 完成
后端服务 (sa-server) 完成
前端服务 (sa-cc) 完成
Nginx 反向代理 完成

26.2 修改记录

日期 修改内容
2025-12-23 初始化 Review 环境配置
2025-12-25 新增前端服务 (sa-cc) 部署章节
2025-12-25 新增 Nginx 反向代理章节,解决跨域问题
2025-12-25 新增 Redis 故障切换与回滚策略章节
2026-04-29 新增节点 ZD-FDFS4 (192.168.3.125) 作为 Worker 节点
2026-04-29 新增 oss.sinoassist.com 路由,直连 3.125:25773不经过网关
2026-04-29 nginx 端口改为 host 模式修复日志 IP新增 TZ=Asia/Shanghai 修正时区
2026-04-29 修复 crm1/crm2 切换后 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检查状态

cd /path/to/redis-review-132
./failover-to-review.sh status

输出示例:

========================================
          当前 Redis 状态
========================================

[192.168.3.132]
  角色: slave
  主节点: 192.168.10.56:6379
  同步状态: down          # 生产故障时显示 down

[192.168.3.133]
  角色: slave
  主节点: 192.168.3.132:6379
  同步状态: up

[192.168.3.134]
  角色: slave
  主节点: 192.168.3.132:6379
  同步状态: up

步骤 2执行完整切换

./failover-to-review.sh full

脚本会自动执行:

  1. 提升 132 为 Master

    SLAVEOF NO ONE
    
  2. 配置 133/134 指向 132

    SLAVEOF 192.168.3.132 6379
    
  3. 部署 Review Sentinel

    • 监控名称:reviewmaster
    • Sentinel 端口26379

步骤 3修改应用配置

切换应用连接到 Review Sentinel

# Spring Boot 配置
spring:
  redis:
    sentinel:
      master: reviewmaster
      nodes: 192.168.3.132:26379,192.168.3.133:26379,192.168.3.134:26379
      password: sino#650
    password: sino#650

或直连模式:

spring:
  redis:
    host: 192.168.3.132
    port: 6379
    password: sino#650

27.5 安全回滚流程

当生产 Redis 恢复后,需要将数据同步回生产并恢复原有架构。

回滚前提条件

  • 132 当前是 Master处于故障切换状态
  • 生产 10.56 已恢复并可连接
  • 所有应用已切换到 Review Sentinel
  • 生产 Sentinel 已停止

步骤 1停止生产 Sentinel手动

重要:必须先在生产服务器上执行!

# SSH 到生产服务器
ssh root@192.168.10.56

# 停止生产 Sentinel
docker stack rm prod_sentinel

原因

  • 回滚步骤会让 10.56 临时变成 132 的从节点
  • 如果 Sentinel 还在运行,会检测到 master 下线并触发故障转移
  • 可能导致脑裂和数据不一致

步骤 2执行安全回滚

./failover-to-review.sh rollback

脚本会执行以下步骤:

2.1 配置 10.56 作为 132 的从节点(反向同步)

# 在 10.56 执行
SLAVEOF 192.168.3.132 6379

2.2 等待数据同步完成

  • 监控 master_repl_offsetslave_repl_offset
  • 偏移量差异 < 100 字节时自动继续
  • 建议此时停止 Review 应用对 Redis 的写入

2.3 切换主从关系

# 将 132 设为只读,防止切换期间数据写入
CONFIG SET min-replicas-to-write 99

# 提升 10.56 为 Master
# 在 10.56 执行
SLAVEOF NO ONE

# 132 重新指向 10.56
SLAVEOF 192.168.10.56 6379

# 恢复 132 隐藏配置
CONFIG SET replica-announced no
CONFIG SET replica-priority 0

# 133/134 指向 132
SLAVEOF 192.168.3.132 6379

步骤 3重启生产 Sentinel

# 在生产服务器执行
docker stack deploy -c sentinel-compose.yml prod_sentinel

步骤 4修改应用配置

将应用连接切换回生产 Sentinel。

27.6 数据一致性保证

阶段 措施 说明
回滚前 反向同步 10.56 先作为 132 的从节点,同步故障期间的数据
回滚中 132 只读 min-replicas-to-write=99 防止切换期间写入
回滚后 偏移量验证 确认数据完全同步

27.7 不安全回滚(不推荐)

如果紧急情况需要快速恢复,可以使用不安全回滚:

./failover-to-review.sh unsafe-rollback

警告

  • 直接让 132 重新指向 10.56
  • 故障期间 132 上的所有写入数据将被覆盖
  • 仅在确认故障期间无重要数据写入时使用

27.8 常见问题

问题 1无法连接生产 Redis

生产环境 192.168.10.56:6379 无法连接

解决:检查生产 Redis 是否已恢复,网络是否通畅。

问题 2同步超时

同步超时,请检查网络和 Redis 状态

解决

  • 检查网络连通性
  • 检查 Redis 日志
  • 可选择强制继续(有数据丢失风险)

问题 3脚本无法远程执行命令

脚本会提示手动在生产服务器执行命令,按提示操作即可。

27.9 操作检查清单

故障切换检查清单

  • 确认生产 Redis 确实故障
  • 执行 ./failover-to-review.sh status 检查状态
  • 执行 ./failover-to-review.sh full 完成切换
  • 验证 132 已成为 Master
  • 验证 Sentinel 正常运行
  • 修改应用配置连接 Review Sentinel
  • 验证应用功能正常

安全回滚检查清单

  • 确认生产 Redis 已恢复
  • 确认所有应用已切换到 Review Sentinel
  • 在生产服务器停止 Sentineldocker stack rm prod_sentinel
  • 建议停止 Review 应用对 Redis 的写入
  • 执行 ./failover-to-review.sh rollback
  • 等待数据同步完成
  • 验证回滚成功
  • 重启生产 Sentinel
  • 修改应用配置连接生产 Sentinel
  • 验证应用功能正常