返回

文章详情

零停机时间的部署与 Docker Compose - 无需 Kubernetes

Hacker News2026年6月24日 20:20

行业中有一种普遍的误解,认为你必须使用 Kubernetes 才能运行一个严肃的生产服务。事实上并不需要。在 StatusDude,我们每分钟处理数千个监控检查,运行多区域的工作进程,并且每天多次部署——这一切都使用 Docker Compose 和 HAProxy 实现。零请求丢失。零停机时间。无需在凌晨三点照看 etcd。但我们并不是从 HAProxy 开始的。我们是从 Traefik 开始的。这一过程持续了大约四个小时。我们首先尝试了 Traefik。Traefik 是基于 Docker 的设置中流行的选择。它通过 Docker 标签自动发现服务,拥有一个流畅的仪表板,文档看起来也毫不费力。我们设置了两个带有 Traefik 标签的后端副本,进行了滚动部署,然后看到了一切崩溃。"服务被多次定义"。我们的第一次部署策略是在过渡期间同时运行一个 backend_new 服务与现有的后端。两个服务都有相同的 Traefik 路由标签——相同的主机规则、相同的服务定义。听起来合理,对吧?你希望在切换期间旧的和新的都能处理流量。Traefik 不同意。它的 Docker 提供者将每个 Compose 服务视为一个独立的配置源。两个具有相同标签的服务?"服务被多次定义"。每个请求都返回 404。没有回退,没有合并,只是完全拒绝路由任何请求。我们重新修改了方法,使用 docker compose --scale backend=4,而不是单独的服务。这避免了标签冲突。但它暴露了下一个问题。缩减规模的竞争。滚动部署策略:扩展到 4 个副本(2 旧 + 2 新),然后缩减到 2(只保留新的)。这很简单。除了 Traefik 的内部路由表更新得不够快。我们从 4 缩减到 2,Traefik 却继续路由到正在关闭的容器。每隔一次请求就返回 502。路由状态比 Docker 的实际状态滞后了几秒钟——足够长的时间掉掉一大部分流量。我们尝试添加延迟。我们尝试在停止容器之前断开它们与网络的连接(这样健康检查会干净地失败,然后在移除前检查)。我们尝试了被动健康检查——添加了它们,然后立即回滚,因为它们过于激进,导致了错误的积极结果。所有这些都不干净。但真正的问题是完全不同的。致命的:不同后端没有重试。这是一个开发人员似乎忽视的已知问题…… https://github.com/traefik/traefik/issues/2723 这是一种情景:在滚动部署期间,你停止一个旧容器。docker stop 发送 SIGTERM。Uvicorn 开始其优雅的关闭,但会有一个窗口——已经处于进行中的请求,或者到达的请求介于停止信号和 Traefik 更新其路由表之间。当请求击中正在关闭的后端时,连接在中途断开。客户端得到一个原始错误——空响应、连接重置、部分内容。我们不能这样。当你报告你的服务和心跳监视器是正常的——我们需要确认!现在,Traefik 对于这个失败请求的处理是:什么都不做。Traefik 的重试中间件存在,但它是在同一个后端上重试。那个正在关闭的。那个将再次失败的。它不会重新分配到健康的后端。请求就这样……丢失了。我们尝试了各种组合:被动健康检查、断开-停止之前断开、不同尝试次数的重试中间件。根本的问题依然存在——Traefik 无法将失败的请求发送到另一个服务器。那天下午,我们拆除了 Traefik,转而使用 HAProxy。你实际上需要什么?让我们简化一下。零停机时间的部署实际上需要什么?多个后端实例——这样你可以在另一个处理流量时替换一个;一个可以在不同后端上重试的负载均衡器——这样即将关闭的容器不会丢失请求;一个逐个替换实例的部署脚本——滚动更新。就是这样。三件事。让我给你展示我们如何做到这三点。第一步:使用 Docker Compose 进行多个副本。Docker Compose 具有内置的 deploy.replicas 设置:# docker-compose.yml services: backend: build: ./backend deploy: replicas: 2 image: myapp-backend expose: - "8000" env_file: .env healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 5s timeout: 5s retries: 3 start_period: 5s restart: unless-stopped。那就是 2 个后端容器在一个共享的 Docker DNS 名称 backend 后面运行。当你在 Docker 网络内解析 backend 时,你会得到两个容器的 IP 地址。一个 Dockerfile,一个镜像,两个容器。没有 Pod 规格,没有部署,没有副本集。第二步:HAProxy 作为负载均衡器。HAProxy 经得起考验,快速,配置可读。但我们选择它的真正原因:选项重新调度。global log stdout format raw local0 info maxconn 4096 defaults mode http timeout connect 3s timeout client 30s timeout server 30s # 关键特性:在不同后端上重试失败的请求。retries 3 选项 redispatch 1 retry-on conn-failure

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡