Watchtower HTTP API 模式指南:用 `/v1/update` 按需触发容器镜像更新
2026/9/20 5:12:19 网站建设 项目流程

Watchtower HTTP API 模式指南:用/v1/update按需触发容器镜像更新

【免费下载链接】watchtowerA process for automating Docker container base image updates.项目地址: https://gitcode.com/gh_mirrors/wa/watchtower

本指南讲解 Watchtower 的 HTTP API 模式:通过--http-api-update启动一个 HTTP 端点,用带 Token 鉴权的请求按需触发容器镜像更新,而非依赖周期轮询。文章覆盖完整的 Docker Compose 部署示例、WATCHTOWER_HTTP_API_TOKEN鉴权机制、按镜像名定向更新的image查询参数用法,并结合 pkg/api 与 pkg/api/update 源码,深入剖析 Token 校验、并发更新锁与过滤链的底层实现,读完即可在生产环境中落地一套"更新由你掌控"的 Watchtower 部署。

为什么需要 HTTP API 模式

默认情况下,Watchtower 会按照--interval(轮询间隔,默认 86400 秒即 24 小时)或--schedule(cron 表达式)周期性地扫描并更新镜像。但在某些场景下,你并不希望 Watchtower 自作主张地按时拉取新镜像,而是希望在合适的时间点(例如维护窗口、发版之后)手动触发一次更新。HTTP API 模式正是为此设计:启用后,Watchtower 暴露一个 HTTP 端点,由外部请求来触发更新。

该模式与 运行多个实例、监控指标 等功能互相独立,可以按需组合使用。

启用 HTTP API 模式

在启动 Watchtower 时加上--http-api-update标志即可启用该模式,对应的环境变量为WATCHTOWER_HTTP_API_UPDATE

以下是一个完整的 docker-compose.yml 示例(原样取自 docs/http-api-mode.md):

version: '3' services: app-monitored-by-watchtower: image: myapps/monitored-by-watchtower labels: - "com.centurylinklabs.watchtower.enable=true" watchtower: image: containrrr/watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock command: --debug --http-api-update environment: - WATCHTOWER_HTTP_API_TOKEN=mytoken labels: - "com.centurylinklabs.watchtower.enable=false" ports: - 8080:8080

这个示例包含几个值得注意的要点:

  • 挂载 Docker 套接字/var/run/docker.sock:/var/run/docker.sock是 Watchtower 与 Docker 守护进程通信的前提,没有它 Watchtower 无法管理任何容器。
  • 标签控制监控范围:被监控的应用容器打了com.centurylinklabs.watchtower.enable=true标签;而 Watchtower 自身容器打了com.centurylinklabs.watchtower.enable=false,避免"看门人更新自己"造成循环。在 internal/flags/flags.go 中,--label-enableWATCHTOWER_LABEL_ENABLE)控制是否只监控打了 enable 标签的容器,而com.centurylinklabs.watchtower.enable=false的排除逻辑则由 pkg/filters/filters.go 中的FilterByDisabledLabel实现。
  • 端口映射8080:8080把 Watchtower 内置 HTTP 服务的 8080 端口暴露到宿主机,从而可以通过localhost:8080访问。

相关的三个核心标志

在 internal/flags/flags.go 中可以看到 HTTP API 模式相关的三个标志及其环境变量对应关系:

命令行标志环境变量类型作用
--http-api-updateWATCHTOWER_HTTP_API_UPDATEBoolean启用 HTTP API 模式,镜像更新必须由 HTTP 请求触发
--http-api-tokenWATCHTOWER_HTTP_API_TOKENString为 HTTP API 请求设置鉴权 Token
--http-api-periodic-pollsWATCHTOWER_HTTP_API_PERIODIC_POLLSBoolean在启用 HTTP API 的同时,仍然保留--interval/--schedule指定的周期轮询

需要说明的是:HTTP API 模式默认会阻止周期轮询。也就是说,仅启用--http-api-update后,--interval--schedule配置的定时更新不会生效,更新只能通过请求触发。如果你希望两者共存——既允许手动触发,也保留定时自动更新——需要额外传递--http-api-periodic-polls

从 cmd/root.go 的启动流程可以验证这一行为:unblockHTTPAPI变量来自--http-api-periodic-polls,只有它为真时,调度器(runUpgradesOnSchedule)才会继续按计划运行;否则 Watchtower 会手动写出启动消息("Periodic runs are not enabled."),并让 HTTP 服务以阻塞方式独占进程(httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI))。

Token 鉴权:防止外部服务误触发更新

为了让外部服务无法"误触"更新,所有 HTTP API 请求都必须在请求头中携带名为Authorization、格式为Bearer <token>的字段,其中<token>的值必须与WATCHTOWER_HTTP_API_TOKEN(或--http-api-token)定义的一致。

上述示例中的鉴权请求如下(原样取自 docs/http-api-mode.md):

curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update

鉴权中间件的源码实现

Token 校验由 pkg/api/api.go 中的RequireToken中间件完成:

func (api *API) RequireToken(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") want := fmt.Sprintf("Bearer %s", api.Token) if auth != want { w.WriteHeader(http.StatusUnauthorized) return } log.Debug("Valid token found.") fn(w, r) } }

关键实现细节:

  • 校验方式是精确字符串比对:请求头中的Authorization值必须严格等于"Bearer " + Token,任何多余或缺失的空格、大小写差异都会导致校验失败。
  • 校验失败时直接返回401 Unauthorized,不会执行后续的更新逻辑。这一点在 pkg/api/api_test.go 中有对应的单元测试覆盖:未提供 Token 时返回 401、Token 无效时返回 401、Token 有效时返回 200。
  • 所有注册到 API 的路由都会经过RequireToken包装:RegisterFuncRegisterHandler(pkg/api/api.go)在注册时即套上鉴权中间件,同时将hasHandlers置为 true,作为后续是否真正启动 HTTP 服务的依据。

Token 为空时的行为

在 pkg/api/api.go 的Start方法中,如果已注册处理器但api.Token为空,Watchtower 会直接以log.Fatal退出,错误信息为:

api token is empty or has not been set. exiting

也就是说,HTTP API 模式强制要求配置 Token,防止出现"裸奔"的无鉴权端点。这从设计层面保证了暴露到宿主机的 8080 端口不会成为任何人都能触发的"更新开关"。

通过文件提供 Token(推荐做法)

Token 属于敏感信息,直接写进 docker-compose.yml 或命令行会留下明文痕迹。在 docs/arguments.md 的 "Secrets/Files" 一节中,Watchtower 支持让http-api-token引用一个文件,取文件内容作为实际 Token:

secrets: access_token: file: access_token services: watchtower: secrets: - access_token environment: - WATCHTOWER_HTTP_API_TOKEN=/run/secrets/access_token

其底层实现位于 internal/flags/flags.go 的GetSecretsFromFiles:启动时(PreRun阶段,见 cmd/root.go)会检测http-api-token的值是否指向一个真实存在的文件,若是则读取文件内容(去除首尾空白)替换原值。支持该文件引用机制的参数还包括notification-urlnotification-email-server-passwordnotification-slack-hook-urlnotification-msteams-hooknotification-gotify-token

端点一览:/v1/update

启用 HTTP API 模式后,当前可用的端点列表如下:

端点方法作用
/v1/updateHTTP(GET/POST 均可,文档示例用 GET)触发本 Watchtower 实例监控的所有容器的更新

在 pkg/api/update/update.go 中,该端点的路径被定义为常量Path: "/v1/update",并在 cmd/root.go 中注册:

updateHandler := update.New(func(images []string) { metric := runUpdatesWithNotifications(filters.FilterByImage(images, filter)) metrics.RegisterScan(metric) }, updateLock) httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle)

当请求到达时,处理函数(Handle)会:

  1. 记录日志 "Updates triggered by HTTP API request.";
  2. 解析 URL 中的image查询参数(详见下一节);
  3. 获取更新锁,确保同一时刻只有一个更新任务在运行;
  4. 调用更新回调,走与定时轮询完全相同的actions.Update更新流水线(包括拉取镜像、重建/重启容器、发送通知等)。

更新锁:防止并发触发互相冲突

HTTP 触发与定时轮询共享同一把更新锁(updateLock,见 cmd/root.go),这一点在注释中写得很明确:"The lock is shared between the scheduler and the HTTP API. It only allows one update to run at a time."

在 pkg/api/update/update.go 中可以看到两种不同的锁行为:

  • 指定了镜像列表len(images) > 0):阻塞等待锁,即使当前已有更新在运行,也会排队等它完成后再执行本次定向更新;
  • 未指定镜像列表:使用非阻塞的select+default,如果另一场更新正在进行,本次请求会被跳过并记录Skipped. Another update already running.的调试日志,避免重复触发全量更新。

定时调度侧(cmd/root.go)也采用同样的锁策略,因此 HTTP 请求与 cron 任务之间不会出现两场更新同时操作同一批容器的竞态。

定向更新:通过image查询参数指定镜像

如果不加任何参数,/v1/update会更新该实例监控的所有容器。若只想更新特定的某些镜像,可以在 URL 中以查询参数image提供镜像名,多个镜像名用英文逗号分隔

curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update?image=foo/bar,foo/baz

上述命令会仅触发foo/barfoo/baz两个镜像的更新(示例原样取自 docs/http-api-mode.md)。

查询参数解析与过滤链

解析逻辑在 pkg/api/update/update.go:从r.URL.Query()["image"]取出所有image参数值,再对每个值按逗号,切分,从而支持"多个参数值 + 逗号分隔"的两种写法。

切分出的镜像列表随后进入 pkg/filters/filters.go 的FilterByImage过滤链:

func FilterByImage(images []string, baseFilter t.Filter) t.Filter { if images == nil { return baseFilter } return func(c t.FilterableContainer) bool { image := strings.Split(c.ImageName(), ":")[0] for _, targetImage := range images { if image == targetImage { return baseFilter(c) } } return false } }

实现要点:

  • 镜像名会先去掉:之后的 tag 部分(例如foo/bar:latest会被归一化为foo/bar),再与请求中的目标镜像名做精确匹配;
  • 该过滤是叠加在基础过滤链之上的:即使请求中指定了image=foo/bar,最终仍会受到启动参数(如--label-enable--disable-containers--scope等)以及容器上com.centurylinklabs.watchtower.enable=false标签的约束,只有"既匹配请求的镜像名、又通过基础过滤链"的容器才会被更新;
  • 未指定image参数时,imagesnilFilterByImage直接返回基础过滤链,即更新所有被监控容器。

与周期轮询、监控指标等其他模式的组合

HTTP API 模式可以与 Watchtower 的其他标志自由组合,常见的搭配包括:

  • --http-api-update --http-api-periodic-polls:手动触发与定时更新共存。此时 HTTP 服务与 cron 调度器并行运行,并通过共享更新锁保证互斥。
  • --http-api-update --http-api-metrics:同时启用更新 API 与 Prometheus 指标端点。--http-api-metricsWATCHTOWER_HTTP_API_METRICS)在 internal/flags/flags.go 中定义,其端点注册与使用说明见 pkg/api/metrics 与 监控指标指南。
  • --http-api-update --interval 300(或--schedule):注意,仅当同时设置--http-api-periodic-polls时,这里的间隔/计划才会生效,否则 HTTP API 模式会按前述规则阻止周期轮询。
  • --http-api-update --scope:结合 运行多个实例 的 scope 机制,让不同的 Watchtower 实例分别监听不同的容器集合,并通过各自的 HTTP 端点独立触发更新。

部署形态:请求触发 vs. 阻塞运行

从 cmd/root.go 可以看到,HTTP 服务的启动方式取决于是否启用周期轮询:

  • 仅 HTTP API(无周期轮询)httpAPI.Start(true)以阻塞方式运行 HTTP 服务,此时 Watchtower 进程的生命周期就是 HTTP 服务的生命周期,调度器不会启动;
  • HTTP API + 周期轮询httpAPI.Start(false)在 goroutine 中启动 HTTP 服务,随后进入正常的调度循环,等待 SIGINT/SIGTERM 信号后优雅退出。

无论哪种形态,HTTP 服务都固定监听:8080端口(pkg/api/api.go 中的http.ListenAndServe(":8080", nil))。源码注释// TODO: make listen port configurable表明端口目前尚不可配置,部署时若宿主机 8080 已被占用,需要通过端口映射(如9080:8080)来规避。

快速验证与故障排查

启用后可通过以下方式快速验证:

  1. 不带 Token 访问curl localhost:8080/v1/update应返回401(HTTP 状态码),说明鉴权生效;
  2. Token 错误curl -H "Authorization: Bearer wrong" localhost:8080/v1/update同样返回401,可对照 pkg/api/api_test.go 的测试用例;
  3. Token 正确curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update应触发更新,并在 Watchtower 日志中看到Updates triggered by HTTP API request.与随后的会话摘要(Session done,含 Scanned/Updated/Failed 计数);
  4. 查看启动日志:启用--debug后,启动时会输出The HTTP API is enabled at :8080.(见 cmd/root.go 的writeStartupMessage),若该行未出现,请确认--http-api-updateWATCHTOWER_HTTP_API_TOKEN均已正确设置。

常见问题与对应检查项:

现象可能原因排查方向
启动即退出,日志报api token is empty or has not been set. exiting未设置 Token配置WATCHTOWER_HTTP_API_TOKEN--http-api-token
请求返回 401Token 值不匹配或请求头格式错误确认Authorization: Bearer <token>的精确格式
请求成功但容器未更新容器不在监控范围(被标签/参数过滤)检查com.centurylinklabs.watchtower.enable标签与过滤参数
请求"无响应效果",日志出现Skipped. Another update already running.另一场更新正在进行(未指定 image 时)稍后重试,或通过image参数指定镜像排队更新
设置了--interval却从不自动更新未启用--http-api-periodic-polls补上该标志或WATCHTOWER_HTTP_API_PERIODIC_POLLS=true

小结

Watchtower 的 HTTP API 模式将"何时更新"的决定权从定时器交还到调用方手中:--http-api-update启用端点,WATCHTOWER_HTTP_API_TOKEN提供强制鉴权,/v1/update支持全量触发与image参数定向触发,--http-api-periodic-polls则允许手动与定时两种方式共存。从 pkg/api/api.go 的 Token 中间件、pkg/api/update/update.go 的并发锁与参数解析,到 cmd/root.go 中与调度器共享的更新锁,整个机制在源码层面环环相扣,既安全(强制鉴权、杜绝并发冲突)又灵活(按需全量或定向更新),适合作为 CI/CD 流水线或运维脚本中"按需升级镜像"的标准入口。

【免费下载链接】watchtowerA process for automating Docker container base image updates.项目地址: https://gitcode.com/gh_mirrors/wa/watchtower

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询