打造Docker Swarm集群服务编排部署指南:从入门到精通
1. 为什么选择 Docker Swarm?
Docker Swarm 是 Docker 原生的集群管理与服务编排工具,简单、轻量、开箱即用。如果你正在寻找一个既能快速上手,又能应对中小型生产环境的解决方案,Swarm 绝对值得一试。相比 Kubernetes 的复杂配置,Swarm 的学习曲线更平缓,但功能却一点不含糊:负载均衡、服务发现、滚动更新、高可用,全都能轻松搞定。
Swarm 的核心优势
-
原生集成:无需额外安装,Docker CLI 直接支持 Swarm 命令。
-
简单易用:几行命令就能搭建集群,适合快速部署。
-
生产可用:支持多节点高可用、自动故障转移。
-
灵活扩展:轻松增加节点,动态调整服务规模。
但它也不是万能的。如果你需要超大规模集群(比如上千节点)或复杂的 CRD(自定义资源定义),Kubernetes 可能更适合。不过,对于中小型团队,Swarm 的“简单即强大”哲学能让你省下不少心力。
2. 基础概念扫盲:Swarm 的核心组件
在动手之前,先搞清楚 Swarm 的几个关键概念,这样后续操作才能心中有数。
-
节点(Node):集群中的每台机器都是一个节点,分为 Manager(管理节点)和 Worker(工作节点)。管理节点负责调度任务、维护集群状态,工作节点跑具体的容器。
-
服务(Service):Swarm 的核心编排单位,定义了容器如何运行、需要几个副本、暴露哪些端口等。
-
任务(Task):服务的具体执行单位,通常是一个容器。
-
栈(Stack):通过 YAML 文件定义一组服务,类似 Kubernetes 的 Helm Chart,方便批量部署。
-
覆盖网络(Overlay Network):Swarm 提供的虚拟网络,节点之间通过它通信,天然支持服务发现。
小贴士:Swarm 的覆盖网络默认加密,通信安全有保障,但高负载场景下可能略影响性能,记得根据业务场景权衡!
3. 环境准备:打造你的 Swarm 试验田
动手之前,先准备好实验环境。以下是我推荐的配置,简单但足以模拟生产场景:
硬件与系统要求
-
机器:至少 3 台虚拟机或云服务器(1 台 Manager,2 台 Worker),每台建议 2 核 4GB 内存。
-
操作系统:Ubuntu 20.04 LTS 或 CentOS 8(其他 Linux 发行版也行,但确保内核支持 Docker)。
-
网络:确保节点间网络互通,防火墙开放以下端口:
-
TCP 2377:管理通信(加密)
-
TCP/UDP 7946:节点间通信
-
UDP 4789:覆盖网络数据传输
-
安装 Docker
在每台机器上安装最新版 Docker(以 Ubuntu 为例):
# 更新包索引
sudo apt-get update
# 安装依赖
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 添加 Docker 软件源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
# 验证安装
docker --version
注意:确保所有节点的 Docker 版本一致,避免兼容性问题。可以用 docker version 检查。
配置节点
为便于管理,给每台机器起个好记的名字:
-
Manager 节点:swarm-manager
-
Worker 节点:swarm-worker1、swarm-worker2
# 设置主机名(以 swarm-manager 为例)
sudo hostnamectl set-hostname swarm-manager
4. 初始化 Swarm 集群
好了,准备工作就绪,接下来正式组建 Swarm 集群!
4.1 在 Manager 节点初始化
在 swarm-manager 上运行以下命令:
docker swarm init --advertise-addr <MANAGER_IP>
--advertise-addr 指定 Manager 的 IP 地址(用 ifconfig 或 ip addr 查看节点 IP)。执行后,你会看到类似以下输出:
Swarm initialized: current node (xxxx) is now a manager.To add a worker to this swarm, run the following command:docker swarm join --token SWMTKN-1-xxxx <MANAGER_IP>:2377To add a manager to this swarm, run the following command:docker swarm join --token SWMTKN-2-xxxx <MANAGER_IP>:2377
复制 docker swarm join 的 Worker 令牌,准备加入 Worker 节点。
4.2 加入 Worker 节点
在 swarm-worker1 和 swarm-worker2 上运行 Manager 提供的 join 命令,例如:
docker swarm join --token SWMTKN-1-xxxx <MANAGER_IP>:2377
成功后,输出会提示节点已加入集群。
4.3 验证集群状态
回到 Manager 节点,运行:
docker node ls
你会看到类似以下输出:
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
xxxx * swarm-manager Ready Active Leader
yyyy swarm-worker1 Ready Active
zzzz swarm-worker2 Ready Active
恭喜!你的 Swarm 集群已经搭建完成!接下来,我们要开始真正的服务编排了。
5. 部署第一个服务:从简单到复杂
让我们从一个简单的 Nginx 服务开始,逐步深入 Swarm 的服务编排功能。
5.1 部署单容器服务
在 Manager 节点运行:
docker service create --name my-nginx -p 8080:80 nginx:latest
这条命令做了啥?
-
--name:给服务命名。
-
-p 8080:80:将主机的 8080 端口映射到容器的 80 端口。
-
nginx:latest:使用最新的 Nginx 镜像。
运行后,用 docker service ls 查看服务状态:
ID NAME MODE REPLICAS IMAGE PORTS
xxxx my-nginx replicated 1/1 nginx:latest *:8080->80/tcp
访问 http://<任一节点IP>:8080,你会看到 Nginx 的欢迎页面!
5.2 扩展服务规模
假设流量增加,单容器不够用,我们扩展到 3 个副本:
docker service scale my-nginx=3
Swarm 会自动在集群中调度 3 个 Nginx 容器,可能分布在不同节点。用 docker service ps my-nginx 查看任务分布:
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
xxxx my-nginx.1 nginx:latest swarm-manager Running Running 5 minutes ago
yyyy my-nginx.2 nginx:latest swarm-worker1 Running Running 4 minutes ago
zzzz my-nginx.3 nginx:latest swarm-worker2 Running Running 4 minutes ago
小技巧:Swarm 的调度器默认使用 spread 策略,尽量将任务均匀分布到节点上,减少单点压力。
5.3 更新服务:零停机滚动更新
假如你想更新 Nginx 到特定版本(比如 nginx:1.21),Swarm 支持无缝滚动更新:
docker service update --image nginx:1.21 my-nginx
Swarm 会逐个替换容器,确保服务不中断。你可以用 --update-delay 10s 设置更新间隔,避免更新过快。
6. 覆盖网络:让服务互联互通
Swarm 的覆盖网络是服务通信的秘密武器。让我们创建一个自定义网络,并部署两个服务验证通信。
6.1 创建覆盖网络
在 Manager 节点运行:
docker network create --driver overlay my-overlay-network
6.2 部署带网络的服务
假设我们要部署一个前端(Nginx)和后端(自定义 API)服务,它们通过 my-overlay-network 通信。
前端服务:
docker service create --name frontend \--network my-overlay-network \-p 8080:80 \nginx:latest
后端服务(假设用一个简单的 Python Flask 应用): 先创建一个简单的 Flask 应用镜像(后续会详细讲如何构建):
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
CMD ["python", "app.py"]
# app.py
from flask import Flask
app = Flask(__name__)@app.route('/')
def hello():return 'Hello from Backend!'if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)
构建并推送镜像到 Docker Hub(或其他镜像仓库):
docker build -t yourusername/my-flask-app:latest .
docker push yourusername/my-flask-app:latest
部署后端服务:
docker service create --name backend \--network my-overlay-network \yourusername/my-flask-app:latest
6.3 测试服务通信
在前端容器中访问后端服务(Swarm 的服务发现基于 DNS):
docker exec -it <frontend-container-id> curl http://backend:5000
你会看到 Hello from Backend! 的输出。Swarm 的覆盖网络让服务间通信像在同一台机器上一样简单!
7. 日志收集:让问题无处遁形
生产环境中,日志是排查问题的命脉。Swarm 默认将容器日志存储在节点本地,但分散的日志管理起来很麻烦。我们需要一个集中化的日志收集方案。
7.1 使用 Docker 日志驱动
Docker 支持多种日志驱动,推荐使用 Fluentd 或 Loki 收集日志。我们以 Fluentd 为例,搭建一个简单的日志收集系统。
步骤 1:部署 Fluentd 服务
创建一个 Fluentd 配置文件 fluentd.conf:
<source>@type forwardport 24224bind 0.0.0.0
</source><match docker.**>@type stdout
</match>
构建 Fluentd 镜像:
# Dockerfile
FROM fluent/fluentd:v1.14-1
COPY fluentd.conf /fluentd/etc/fluentd.conf
docker build -t yourusername/fluentd:latest .
docker push yourusername/fluentd:latest
在 Stack 文件中添加 Fluentd 服务:
version: '3.8'services:fluentd:image: yourusername/fluentd:latestdeploy:replicas: 1placement:constraints:- node.role == managerports:- "24224:24224"networks:- wp-networkwordpress:image: wordpress:latestdeploy:replicas: 3ports:- "8080:80"logging:driver: fluentdoptions:fluentd-address: fluentd:24224networks:- wp-networkmysql:image: mysql:8.0deploy:replicas: 1volumes:- wp-data:/var/lib/mysqllogging:driver: fluentdoptions:fluentd-address: fluentd:242Så24networks:- wp-networknetworks:wp-network:driver: overlayvolumes:wp-data:
步骤 2:部署并验证
docker stack deploy -c wordpress-stack.yml wp-stack
Fluentd 会收集 WordPress 和 MySQL 的日志,输出到标准输出。你可以通过 docker service logs wp-stack_fluentd 查看收集的日志。
进阶玩法:将 Fluentd 配置为将日志转发到 Elasticsearch 或 Loki,再用 Grafana 可视化,打造一个强大的日志分析平台。
8. 监控:让集群健康一目了然
没有监控的集群就像开夜车没开大灯,迟早要撞墙。我们需要实时了解集群的健康状态、资源使用情况和服务性能。
8.1 使用 Prometheus 和 Grafana
Prometheus 是监控领域的明星,搭配 Grafana 的仪表盘,简直是运维的“千里眼”。我们用 Stack 文件部署一个监控堆栈。
监控 Stack 文件:monitoring-stack.yml
version: '3.8'services:prometheus:image: prom/prometheus:v2.37.0deploy:replicas: 1placement:constraints:- node.role == managervolumes:- prometheus-data:/prometheus- ./prometheus.yml:/etc/prometheus/prometheus.ymlports:- "9090:9090"networks:- monitoring-networknode-exporter:image: prom/node-exporter:v1.3.1deploy:mode: globalnetworks:- monitoring-networkgrafana:image: grafana/grafana:8.5.0deploy:replicas: 1ports:- "3000:3000"networks:- monitoring-networkenvironment:- GF_SECURITY_ADMIN_PASSWORD=admin123networks:monitoring-network:driver: overlayvolumes:prometheus-data:
Prometheus 配置文件:prometheus.yml
global:scrape_interval: 15sscrape_configs:- job_name: 'prometheus'static_configs:- targets: ['prometheus:9090']- job_name: 'node-exporter'static_configs:- targets: ['node-exporter:9100']- job_name: 'docker'static_configs:- targets: ['<manager-ip>:9323']
注意:Docker 默认不暴露 metrics 端点,需在 Manager 节点的 /etc/docker/daemon.json 中启用:
{"metrics-addr": "0.0.0.0:9323","experimental": true
}
重启 Docker 服务:
sudo systemctl restart docker
部署监控堆栈:
docker stack deploy -c monitoring-stack.yml monitoring
访问 http://<manager-ip>:3000,用默认账户(admin/admin123)登录 Grafana,导入 Docker 和 Node Exporter 的仪表盘模板(Grafana 社区有现成的模板,ID 比如 1860 或 11074),你就能看到节点 CPU、内存、容器状态等实时数据。
实战经验:为关键服务添加自定义 metrics(比如 WordPress 的请求延迟),可以进一步优化监控效果。
9. 负载均衡:让流量分配更聪明
Swarm 内置的负载均衡器(基于 IPVS)已经很强大,但我们可以通过一些配置让它更高效。
9.1 外部负载均衡
如果你的集群暴露多个服务,建议在 Swarm 前加一层 Nginx 或 HAProxy 作为外部负载均衡器。例如,部署一个 Nginx 服务作为反向代理:
version: '3.8'services:proxy:image: nginx:latestdeploy:replicas: 1placement:constraints:- node.role == managerports:- "80:80"volumes:- ./nginx.conf:/etc/nginx/nginx.confnetworks:- wp-networknetworks:wp-network:driver: overlay
Nginx 配置文件:nginx.conf
worker_processes 1;events { worker_connections 1024; }http {upstream wordpress {server wordpress:80;}server {listen 80;server_name _;location / {proxy_pass http://wordpress;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}}
}
部署后,外部流量通过 Nginx 打到 WordPress 服务,Swarm 内部负载均衡器再将流量分发到多个 WordPress 容器。
9.2 优化 Swarm 内置负载均衡
Swarm 的负载均衡默认是基于 DNS 的轮询(round-robin)。如果需要更复杂的策略(如基于权重),可以结合外部负载均衡器或调整服务副本数。
小技巧:用 docker service update --endpoint-mode vip(默认模式)确保负载均衡稳定。如果服务通信频繁,考虑用 --endpoint-mode dnsrr 启用 DNS 轮询,减少 VIP 开销。
10. 安全加固:让你的集群固若金汤
生产环境的 Swarm 集群就像一座城堡,功能再强大也得防住“入侵者”。我们将从 Secrets 管理、TLS 加密 和 访问控制 三方面加固你的集群。
10.1 Secrets 管理:保护敏感数据
数据库密码、API 密钥等敏感信息不能明文写在 Stack 文件或环境变量里。Swarm 提供了 Secrets 功能,让你安全地存储和分发敏感数据。
创建 Secrets
假设我们的电商系统需要保护 Redis 的密码。先在 Manager 节点创建 Secret:
echo "supersecretpassword" | docker secret create redis_password -
检查 Secret 是否创建成功:
docker secret ls
输出类似:
ID NAME CREATED UPDATED
xxxx redis_password 5 minutes ago 5 minutes ago
在 Stack 文件中使用 Secrets
修改 ecommerce-stack.yml,将 Redis 的密码通过 Secret 注入:
version: '3.8'services:frontend:image: yourusername/ecommerce-frontend:latestdeploy:replicas: 3update_config:parallelism: 1delay: 10srestart_policy:condition: on-failureports:- "3000:3000"networks:- ecommerce-networkdepends_on:- backendbackend:image: yourusername/ecommerce-backend:latestdeploy:replicas: 2update_config:parallelism: 1delay: 10srestart_policy:condition: on-failureenvironment:REDIS_HOST: redisREDIS_PASSWORD_FILE: /run/secrets/redis_passwordsecrets:- redis_passwordnetworks:- ecommerce-networkdepends_on:- redisredis:image: redis:6.2deploy:replicas: 1placement:constraints:- node.role == managercommand: redis-server --requirepass $(cat /run/secrets/redis_password)volumes:- redis-data:/datasecrets:- redis_passwordnetworks:- ecommerce-networknetworks:ecommerce-network:driver: overlayvolumes:redis-data:secrets:redis_password:external: true
代码调整
修改后端代码以读取 Secret 文件:
// backend/index.js
const express = require('express');
const redis = require('redis');
const fs = require('fs');
const app = express();const password = fs.readFileSync('/run/secrets/redis_password', 'utf8').trim();
const client = redis.createClient({ url: `redis://:${password}@redis:6379`
});
client.connect();app.get('/products', async (req, res) => {const cached = await client.get('products');if (cached) return res.json(JSON.parse(cached));const products = [{ id: 1, name: 'Laptop', price: 999 },{ id: 2, name: 'Phone', price: 499 }];await client.set('products', JSON.stringify(products), { EX: 3600 });res.json(products);
});app.listen(3001, () => console.log('Backend running on port 3001'));
重新构建并推送镜像:
docker build -t yourusername/ecommerce-backend:latest ./backend
docker push yourusername/ecommerce-backend:latest
docker stack deploy -c ecommerce-stack.yml ecommerce
安全提示:Secrets 存储在 Manager 节点的 Raft 数据库中,加密传输到容器。定期轮换 Secrets(删除旧的,创建新的),并限制 Manager 节点的访问权限。
10.2 TLS 加密:保护网络通信
Swarm 默认加密管理流量(2377 端口)和覆盖网络,但外部流量(如用户访问前端的 HTTP 请求)需要额外的 TLS 保护。我们用 Nginx 反向代理加上 Let’s Encrypt 的免费证书来实现 HTTPS。
部署 Certbot 获取证书
在 Manager 节点运行 Certbot 容器获取证书:
docker run -it --rm -v certs:/etc/letsencrypt -p 80:80 certbot/certbot certonly --standalone -d yourdomain.com
证书会存储在 certs 卷中。
更新 Nginx 配置
修改 nginx.conf 支持 HTTPS:
worker_processes 1;events { worker_connections 1024; }http {upstream frontend {server frontend:3000;}server {listen 80;server_name yourdomain.com;return 301 https://$host$request_uri;}server {listen 443 ssl;server_name yourdomain.com;ssl_certificate /etc/nginx/certs/live/yourdomain.com/fullchain.pem;ssl_certificate_key /etc/nginx/certs/live/yourdomain.com/privkey.pem;location / {proxy_pass http://frontend;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}}
}
更新 ecommerce-stack.yml 加入 Nginx 服务:
version: '3.8'services:proxy:image: nginx:latestdeploy:replicas: 1placement:constraints:- node.role == managerports:- "80:80"- "443:443"volumes:- certs:/etc/nginx/certs- ./nginx.conf:/etc/nginx/nginx.confnetworks:- ecommerce-networkfrontend:image: yourusername/ecommerce-frontend:latestdeploy:replicas: 3networks:- ecommerce-networkbackend:image: yourusername/ecommerce-backend:latestdeploy:replicas: 2environment:REDIS_HOST: redisREDIS_PASSWORD_FILE: /run/secrets/redis_passwordsecrets:- redis_passwordnetworks:- ecommerce-networkredis:image: redis:6.2deploy:replicas: 1command: redis-server --requirepass $(cat /run/secrets/redis_password)volumes:- redis-data:/datasecrets:- redis_passwordnetworks:- ecommerce-networknetworks:ecommerce-network:driver: overlayvolumes:redis-data:certs:secrets:redis_password:external: true
部署并验证
docker stack deploy -c ecommerce-stack.yml ecommerce
访问 https://yourdomain.com,你会看到安全的电商前端页面。注意:定期用 Certbot 续期证书(每 90 天),可以用定时任务自动化。
10.3 访问控制:最小权限原则
-
限制 Docker API 访问:默认情况下,Docker 守护进程监听 unix:///var/run/docker.sock。如果暴露了 TCP 端口(如 2375),务必用防火墙限制访问:
sudo ufw allow from <trusted-ip> to any port 2375
-
RBAC:Swarm 本身不提供细粒度 RBAC,但可以通过限制 Manager 节点的 SSH 访问和 Docker CLI 权限来实现。例如,只允许特定用户运行 docker 命令:
sudo groupadd docker
sudo usermod -aG docker <username>
-
节点隔离:用 node.labels 将敏感服务(如数据库)限制在特定节点,防止非授权节点运行。
11. 性能优化:榨干集群的每一分潜力
Swarm 集群的性能直接影响服务的响应速度和用户体验。我们从 容器资源调优 和 网络优化 两方面入手。
11.1 容器资源调优
在 Part 2,我们为服务设置了 CPU 和内存限制,但这只是基础。以下是一些进阶技巧:
-
动态调整资源:根据负载动态调整副本数。Swarm 没有内置的 HPA(水平自动扩展),但可以用外部工具(如 Prometheus + 自定义脚本)实现。例如,监控前端服务的 CPU 使用率,超过 70% 时增加副本:
#!/bin/bash
CPU_USAGE=$(curl -s http://<prometheus-ip>:9090/api/v1/query?query=rate(container_cpu_usage_seconds_total{service="ecommerce_frontend"}[5m]) | jq '.data.result[0].value[1]' | awk '{print $1*100}')
if (( $(echo "$CPU_USAGE > 70" | bc -l) )); thendocker service scale ecommerce_frontend=5
fi
将脚本加入 cron 每分钟运行。
-
内存优化:避免容器内存泄漏。定期用 docker stats 检查容器内存使用情况,必要时重启服务:
docker service update --force ecommerce_frontend
11.2 网络优化
Swarm 的覆盖网络虽然方便,但高流量场景下可能成为瓶颈。优化方法:
-
减少跨节点通信:用 placement 约束将相关服务调度到同一节点,降低网络延迟。例如,将前端和后端放在同一节点:
frontend:image: yourusername/ecommerce-frontend:latestdeploy:replicas: 3placement:constraints:- node.labels.type == appbackend:image: yourusername/ecommerce-backend:latestdeploy:replicas: 2placement:constraints:- node.labels.type == app
给节点加标签:
docker node update --label-add type=app swarm-worker1
-
启用压缩:在 Nginx 反向代理中启用 Gzip 压缩,减少数据传输量:
http {gzipocios2gzip on;gzip_types text/plain text/css application/json;
}
-
调整 MTU:如果网络性能不佳,检查节点的 MTU 设置,确保与覆盖网络一致(默认 1500)。
12. 复杂分布式系统:带消息队列的架构
让我们部署一个更复杂的系统,加入 RabbitMQ 作为消息队列,用于异步处理订单。
Stack 文件:ecommerce-mq-stack.yml
version: '3.8'services:proxy:image: nginx:latestdeploy:replicas: 1ports:- "80:80"- "443:443"volumes:- certs:/etc/nginx/certs- ./nginx.conf:/etc/nginx/nginx.confnetworks:- ecommerce-networkfrontend:image: yourusername/ecommerce-frontend:latestdeploy:replicas: 3networks:- ecommerce-networkbackend:image: yourusername/ecommerce-backend:latestdeploy:replicas: 2environment:REDIS_HOST: redisREDIS_PASSWORD_FILE: /run/secrets/redis_passwordRABBITMQ_HOST: rabbitmqsecrets:- redis_passwordnetworks:- ecommerce-networkredis:image: redis:6.2deploy:replicas: 1command: redis-server --requirepass $(cat /run/secrets/redis_password)volumes:- redis-data:/datasecrets:- redis_passwordnetworks:- ecommerce-networkrabbitmq:image: rabbitmq:3.9-managementdeploy:replicas: 1ports:- "15672:15672"networks:- ecommerce-networkenvironment:RABBITMQ_DEFAULT_USER: guestRABBITMQ_DEFAULT_PASS: guestnetworks:ecommerce-network:driver: overlayvolumes:redis-data:certs:secrets:redis_password:external: true
后端代码调整
添加 RabbitMQ 生产者,处理订单:
// backend/index.js
const amqp = require('amqplib');
const express = require('express');
const redis = require('redis');
const fs = require('fs');
const app = express();const password = fs.readFileSync('/run/secrets/redis_password', 'utf8').trim();
const client = redis.createClient({ url: `redis://:${password}@redis:6379` });
client.connect();async function sendToQueue(order) {const conn = await amqp.connect('amqp://guest:guest@rabbitmq:5672');const channel = await conn.createChannel();await channel.assertQueue('orders');channel.sendToQueue('orders', Buffer.from(JSON.stringify(order)));await channel.close();await conn.close();
}app.get('/products', async (req, res) => {const cached = await client.get('products');if (cached) return res.json(JSON.parse(cached));const products = [{ id: 1, name: 'Laptop', price: 999 },{ id: 2, name: 'Phone', price: 499 }];await client.set('products', JSON.stringify(products), { EX: 3600 });res.json(products);
});app.post('/order', express.json(), async (req, res) => {await sendToQueue(req.body);res.json({ message: 'Order placed' });
});app.listen(3001, () => console.log('Backend running on port 3001'));
部署并测试
docker stack deploy -c ecommerce-mq-stack.yml ecommerce
访问 RabbitMQ 管理界面 http://<node-ip>:15672(默认用户/密码:guest/guest),查看订单队列。发送一个 POST 请求到 /order,检查队列是否收到消息。