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-enable(WATCHTOWER_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-update | WATCHTOWER_HTTP_API_UPDATE | Boolean | 启用 HTTP API 模式,镜像更新必须由 HTTP 请求触发 |
--http-api-token | WATCHTOWER_HTTP_API_TOKEN | String | 为 HTTP API 请求设置鉴权 Token |
--http-api-periodic-polls | WATCHTOWER_HTTP_API_PERIODIC_POLLS | Boolean | 在启用 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包装:RegisterFunc与RegisterHandler(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-url、notification-email-server-password、notification-slack-hook-url、notification-msteams-hook、notification-gotify-token。
端点一览:/v1/update
启用 HTTP API 模式后,当前可用的端点列表如下:
| 端点 | 方法 | 作用 |
|---|---|---|
/v1/update | HTTP(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)会:
- 记录日志 "Updates triggered by HTTP API request.";
- 解析 URL 中的
image查询参数(详见下一节); - 获取更新锁,确保同一时刻只有一个更新任务在运行;
- 调用更新回调,走与定时轮询完全相同的
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/bar与foo/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参数时,images为nil,FilterByImage直接返回基础过滤链,即更新所有被监控容器。
与周期轮询、监控指标等其他模式的组合
HTTP API 模式可以与 Watchtower 的其他标志自由组合,常见的搭配包括:
--http-api-update --http-api-periodic-polls:手动触发与定时更新共存。此时 HTTP 服务与 cron 调度器并行运行,并通过共享更新锁保证互斥。--http-api-update --http-api-metrics:同时启用更新 API 与 Prometheus 指标端点。--http-api-metrics(WATCHTOWER_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)来规避。
快速验证与故障排查
启用后可通过以下方式快速验证:
- 不带 Token 访问:
curl localhost:8080/v1/update应返回401(HTTP 状态码),说明鉴权生效; - Token 错误:
curl -H "Authorization: Bearer wrong" localhost:8080/v1/update同样返回401,可对照 pkg/api/api_test.go 的测试用例; - Token 正确:
curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update应触发更新,并在 Watchtower 日志中看到Updates triggered by HTTP API request.与随后的会话摘要(Session done,含 Scanned/Updated/Failed 计数); - 查看启动日志:启用
--debug后,启动时会输出The HTTP API is enabled at :8080.(见 cmd/root.go 的writeStartupMessage),若该行未出现,请确认--http-api-update与WATCHTOWER_HTTP_API_TOKEN均已正确设置。
常见问题与对应检查项:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
启动即退出,日志报api token is empty or has not been set. exiting | 未设置 Token | 配置WATCHTOWER_HTTP_API_TOKEN或--http-api-token |
| 请求返回 401 | Token 值不匹配或请求头格式错误 | 确认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),仅供参考