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