容器里的 Python 服务:用标准库做零依赖健康检查

举报
yd_51737554 发表于 2026/10/29 10:05:33 2026/10/29
【摘要】 服务上线之后,最先坏掉的往往不是业务接口,而是健康检查。探针路径写错、检查逻辑连了真实数据库、超时时间和编排平台默认值打架,容器会被反复重启,日志里却只有一句 connection refused。这篇笔记只使用 Python 标准库,给一个普通 HTTP 服务加一个可单独探测的 /healthz,并说明容器里应该怎么配。 为什么不要在业务端口里顺手做检查很多人把健康检查挂在业务路由上,例如...

服务上线之后,最先坏掉的往往不是业务接口,而是健康检查。探针路径写错、检查逻辑连了真实数据库、超时时间和编排平台默认值打架,容器会被反复重启,日志里却只有一句 connection refused。这篇笔记只使用 Python 标准库,给一个普通 HTTP 服务加一个可单独探测的 /healthz,并说明容器里应该怎么配。

为什么不要在业务端口里顺手做检查

很多人把健康检查挂在业务路由上,例如访问首页返回 200。这样有三个问题。第一,首页可能依赖模板、静态资源和登录态,探针一多就把这些成本放大。第二,编排系统的探针来自内网,不该走进鉴权和限流。第三,进程还活着但已经不能接新请求时,首页仍可能返回缓存页面,探针会误判为健康。

更稳妥的做法是单独开一个只监听本机的端口,专门回答“进程是否还能工作”。它不访问外网,不写数据库,只看本进程里维护的一个状态位。

一个最小实现

下面的代码把业务状态收成一个线程安全的标志。启动时先标记为未就绪,初始化完成后再打开。探针只读这个标志,因此即使业务端口堵住,检查端口仍然能在几十毫秒内返回。

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import threading

class State:
    def __init__(self):
        self._ready = False
        self._lock = threading.Lock()

    def set_ready(self, value: bool) -> None:
        with self._lock:
            self._ready = value

    def ready(self) -> bool:
        with self._lock:
            return self._ready

STATE = State()

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/healthz":
            self.send_error(404)
            return
        ok = STATE.ready()
        body = b"ok" if ok else b"starting"
        self.send_response(200 if ok else 503)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        return

def serve():
    httpd = ThreadingHTTPServer(("127.0.0.1", 8081), Handler)
    httpd.serve_forever()

if __name__ == "__main__":
    threading.Thread(target=serve, daemon=True).start()
    STATE.set_ready(True)
    threading.Event().wait()

监听地址用 127.0.0.1,是为了避免这个端口被容器网络以外的人扫到。容器编排如果从旁路容器或宿主机来探测,就改成 0.0.0.0,并只在安全组里放行探针网段。

和容器探针配合

存活探针看进程是否该被杀掉,就绪探针看是否可以接流量。两者不要共用同一个过重的检查。建议:

  • liveness 访问 /healthz,失败阈值设为 3 次,间隔 10 秒,启动宽限 20 秒。初始化超过这个时间就单独加长宽限,不要把间隔拉到一分钟,否则真死了也要很久才被拉起。
  • readiness 使用同一个路径即可,因为未就绪时我们返回 503。流量会暂时摘掉,进程不会被杀。
  • 不要在检查里发外部请求。数据库抖动应该体现在业务错误率上,而不是让探针把所有副本同时打成不健康。

如果运行时需要主动熔断,比如连续写失败超过阈值,就调用 STATE.set_ready(False)。就绪探针立刻失败,负载均衡把流量切走,进程仍留在那里排空已有请求。恢复后重新 set_ready(True)。

上线前自己点一次

在容器里执行 curl 访问 127.0.0.1 的 8081 端口。期望看到 HTTP 200 和正文 ok。把 set_ready 那一行临时注释掉,应看到 503 和 starting。这两次结果对上,再交给编排平台。检查端口和业务端口分开,半夜被误重启的次数就会少一点。初始化日志也建议打在标准输出里,方便和探针失败时间对齐。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。