- 示例工程
- 云原生
- 后端
【免费下载链接】example-voting-app
Example distributed app composed of multiple containers for Docker, Compose, Swarm, and Kubernetes
本指南以example-voting-app仓库中的 README.md 为核心脉络,完整介绍这套多语言分布式示例应用的架构组成、三种主流容器编排方式(Docker Compose / Docker Swarm / Kubernetes)的部署与访问方法,并结合仓库源码逐层剖析投票数据从前端到 Redis 队列、再到 Postgres 与实时结果页的完整链路。读完本文,你将掌握一套可直接复制运行的分布式应用示例的部署命令、端口规划、健康检查与去重机制,并理解消息队列、持久化与多语言容器协作的工程实践。
项目概览:一个跨语言的分布式投票应用
Example Voting App 是一个简单但五脏俱全的分布式应用,运行在多套 Docker 容器之上。项目采用 Python、Node.js、.NET 三种语言分别实现不同职责的组件,并用 Redis 作为消息队列、Postgres 作为持久化存储(README 明确说明了技术栈组合)。
整个应用由五个核心服务组成,它们通过容器网络相互协作,构成一条完整的投票数据处理流水线:
| 组件 | 语言/运行时 | 职责 |
|---|---|---|
| vote 前端 | Python (Flask + Gunicorn) | 提供两个选项的投票页面 |
| redis | Redis (alpine) | 暂存收集的新投票(消息队列) |
| worker | .NET (C#) | 消费 Redis 中的投票并写入 Postgres |
| db | PostgreSQL (alpine) | 通过 Docker volume 持久化投票数据 |
| result 前端 | Node.js (Express + Socket.IO) | 实时展示投票统计结果 |
从仓库的目录布局可以清晰地看到各服务的源码与部署文件被独立组织:前端与后端代码分别位于 vote/、worker/ 与 result/,而 Kubernetes 清单统一放在 k8s-specifications/ 目录下。
快速开始:使用 Docker Compose 一键启动
环境准备
- Mac / Windows:安装 Docker Desktop,Docker Compose 会随安装自动附带;
- Linux:确保安装了最新版本的 Docker Compose。
启动命令
在仓库根目录执行:
docker compose up启动后:
vote投票应用运行在 http://localhost:5000result结果页运行在 http://localhost:5001
Compose 文件的服务编排细节
仓库中的 docker-compose.yml 采用当前 Compose Spec 规范(文件中注明 v1.27+ 版本即可使用,v2/v3 已合并),其编排设计值得逐一解读:
服务构建与本地开发挂载。vote服务通过build.context: ./vote与build.target: dev构建,把 vote/ 目录挂载进容器/usr/local/app,配合 vote/Dockerfile 中的dev阶段(安装watchdog、设置FLASK_ENV=development、以python app.py启动)实现本地开发热更新;result服务则通过entrypoint: nodemon --inspect=0.0.0.0 server.js使用 nodemon 进行本地开发调试。
健康检查与依赖顺序。Compose 文件为所有服务配置了depends_on与condition: service_healthy,强制容器按依赖顺序就绪:
vote依赖 redis 健康;result依赖 db 健康;worker同时依赖 redis 与 db 健康。
健康检查脚本统一放在 healthchecks/ 目录并挂载进容器:redis使用 redis.sh,db使用 postgres.sh。例如 postgres.sh 通过psql执行SELECT 1并校验返回结果为1来判断数据库外部可连通性(脚本强制使用--host走网络而非本地 unix socket,确保测试的是“外部可达性”)。
双网段隔离。Compose 定义了front-tier与back-tier两个网络:vote/result/seed 同时接入前后两个网络,而 worker、redis、db 仅在back-tier中。这种“前端服务可访问后端,后端服务彼此隔离于前端”的设计模拟了生产环境的网络分层。
种子数据 Profile。seed服务使用profiles: ["seed"]声明,默认不会启动;如需灌入测试数据,使用docker compose --profile seed up -d显式开启,它会在vote健康后运行 seed-data/ 中的脚本向应用灌入投票数据,且restart: "no"保证只执行一次。
扩展运行:Docker Swarm 与 docker-stack.yml
如果希望在多节点 Swarm 集群上运行,README 给出了完整流程。
初始化 Swarm(如尚未初始化)
docker swarm init部署 Stack
docker stack deploy --compose-file docker-stack.yml voteSwarm 清单的要点
仓库中的 docker-stack.yml 专为 Swarm 设计,文件注释说明了两个关键背景:
- 该文件不能在 Compose 下直接使用,因为多个副本会争抢绑定同一端口;
- Swarm 目前不支持 Compose Spec,因此文件锁定为较旧的
version: "3.9"。
该清单使用预构建镜像dockersamples/examplevotingapp_vote等,而不是本地构建,并利用deploy.replicas实现水平扩展:vote与worker均配置为 2 个副本,result为 1 个副本。网络拆分为frontend/backend,worker同时挂接两个网络以桥接投票与持久化两侧。
注意 Swarm 模式下端口仅暴露在 manager 节点上(vote5000:80、result5001:80),访问地址取决于集群节点地址而非 localhost。
部署到 Kubernetes:k8s-specifications 清单解析
一键部署与清理
k8s-specifications/ 目录存放着全部服务的 YAML 清单。README 给出的操作步骤为:
# 创建 Deployment 与 Service(在当前 namespace 下创建,未修改则为 default) kubectl create -f k8s-specifications/ # 清理资源 kubectl delete -f k8s-specifications/部署后,voteWeb 应用可通过集群每个节点的31000端口访问,resultWeb 应用可通过31001端口访问。
各服务清单对应关系
| 服务 | Deployment | Service |
|---|---|---|
| vote | vote-deployment.yaml | vote-service.yaml |
| result | result-deployment.yaml | result-service.yaml |
| worker | worker-deployment.yaml | — |
| db | db-deployment.yaml | db-service.yaml |
| redis | redis-deployment.yaml | redis-service.yaml |
关键清单细节
NodePort 对外暴露。vote与result的 Service 均使用type: NodePort:vote 服务port: 5000 → targetPort: 80 → nodePort: 31000,result 服务port: 5001 → targetPort: 80 → nodePort: 31001,这就对应了 README 中“端口 31000 / 31001”的说明。
内部通信服务。db与redis的 Service 使用type: ClusterIP,仅暴露在集群内部(分别为 5432 与 6379),供 vote、worker、result 通过服务名(如db、redis)访问。
存储策略的差异。Compose 场景下db使用命名 volumedb-data持久化到/var/lib/postgresql/data;而在 k8s 清单中,db-deployment.yaml 与 redis-deployment.yaml 均使用emptyDir: {}卷,意味着数据随 Pod 生命周期存亡——这是示例场景下“无持久化”的简化取舍。
镜像来源。Kubernetes 清单使用预构建镜像(如dockersamples/examplevotingapp_vote、postgres:15-alpine、redis:alpine),部署前需确保集群节点可拉取这些公共镜像。
源码级解读:从投票到结果展示的完整链路
1. vote 前端:写入 Redis 队列
vote/app.py 是 Python/Flask 投票入口。其核心逻辑为:
- 两个选项通过环境变量
OPTION_A/OPTION_B配置,默认值为Cats与Dogs; - 每个客户端首次访问时生成一个 64 位随机
voter_id(hex(random.getrandbits(64))),并通过 cookie 下发,用于后续去重; - 提交 POST 请求后,将
{voter_id, vote}序列化为 JSON,通过redis.rpush('votes', data)从列表右端推入Redis 的votes列表,Redis 在此扮演消息队列角色; - Redis 连接采用 Flask 的
g上下文按请求复用(Redis(host="redis", db=0, socket_timeout=5))。
if request.method == 'POST': redis = get_redis() vote = request.form['vote'] data = json.dumps({'voter_id': voter_id, 'vote': vote}) redis.rpush('votes', data)生产模式下 vote/Dockerfile 的final阶段以 Gunicorn 多 worker 运行(--workers 4),并为健康检查安装了curl。
2. worker:消费队列并写入 Postgres
worker/Program.cs 是 .NET 消费者,实现了“拉取-处理-保活”的循环:
- 使用
redis.ListLeftPopAsync("votes")从列表左端弹出消息(与 rpush 构成 FIFO 队列语义),每 100ms 轮询一次以防 CPU 空转; - 解析 JSON 后调用
UpdateVote写入 Postgres:先尝试INSERT,若因voter_id唯一约束冲突抛出DbException,则转为UPDATE——这一“插入冲突则更新”的幂等策略保证了同一浏览器不会重复计票,正是 README 所述“每个客户端浏览器只接受一票”的落地实现; - 启动时自动执行
CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL)建表; - 内置容错:Redis/DB 连接断开时打印提示并无限重连(
Waiting for db/Waiting for redis),并通过对 Postgres 定期执行SELECT 1保持连接活跃。
3. result 前端:实时聚合与展示
result/server.js 是 Node.js 结果服务,负责把投票数据实时推送到浏览器:
- 通过
pg.Pool连接 Postgres(连接串postgres://postgres:postgres@db/postgres),并使用async.retry(1000 次 × 1s 间隔)等待数据库就绪; - 每 1 秒执行一次
SELECT vote, COUNT(id) AS count FROM votes GROUP BY vote,将统计结果封装后通过 Socket.IO 以scores事件广播; - 浏览器端 result/views/index.html 配合 result/views/app.js 订阅事件并实时刷新柱状图。
4. 测试与辅助资源
- result/tests/ 提供针对 result 服务的渲染与功能测试(tests.sh、render.js),配合 docker-compose.test.yml 使用;
- seed-data/ 包含 generate-votes.sh 与 make-data.py,用于批量生成投票数据。
注意事项与适用边界
- 投票去重机制:应用通过浏览器 cookie 中的
voter_id结合数据库唯一约束实现“一客户端一票”,不统计同一客户端的重复投票; - 示例定位:README 明确强调,这不是一个架构设计完美的生产级分布式应用,而是展示队列、持久化等各类组件与语言在 Docker 中以基础方式协作的简单示例;
- 部署前提:Kubernetes 清单中的 NodePort 与预构建镜像要求集群节点可达;Swarm 清单要求已初始化 Swarm 且不能与 Compose 混用;Compose 清单需要 Docker Compose v1.27+。
无论是学习容器编排、体验消息队列与持久化协作,还是作为多语言微服务教学模板,这套 Example Voting App 都是一份可直接运行的参考实现。
- 示例工程
- 云原生
- 后端
【免费下载链接】example-voting-app
Example distributed app composed of multiple containers for Docker, Compose, Swarm, and Kubernetes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考