Dokku Build Tracking 构建跟踪机制详解:结构化记录、实时日志与保留策略实战指南

发布时间:2026/9/11 6:36:40
Dokku Build Tracking 构建跟踪机制详解:结构化记录、实时日志与保留策略实战指南 Dokku Build Tracking 构建跟踪机制详解结构化记录、实时日志与保留策略实战指南【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku本文以 Dokku 0.38.0 新增的 builds 插件为核心系统讲解构建记录Build Records的生成原理、磁盘存储结构、builds:*系列命令的完整用法以及保留策略Retention的配置与清理机制。读完本文你将能够追踪任意一次部署git push、ps:rebuild、config:set等的完整生命周期实时流式查看构建日志在构建卡死时安全取消并自主管理构建历史的磁盘占用。构建跟踪解决了什么问题在 Dokku 中每次部署——无论是通过git push触发、ps:rebuild重建、ps:restart重启还是config:set引发的自动重新部署甚至git:from-archive/git:from-image/git:sync/git:load-image等 git 相关操作——都会在磁盘上留下一条结构化的构建记录structured build record。这一功能官方文档标记为New as of 0.38.0参见 docs/advanced-usage/builds.md让运维人员可以查看宿主机上当前正在部署的构建查询任意历史构建的结果成功、失败、取消、遗留流式查看某次构建捕获的完整日志而不必依赖journalctl的日志轮转与检索。它彻底改变了以往构建日志散落在 journald 中、历史结果无从查证的运维体验。从源码结构看builds是 Dokku 的核心插件之一见 plugins/builds/plugin.toml由 Go 实现并暴露 7 个子命令与 6 个内部触发器见 plugins/builds/Makefile。命令总览builds插件向dokkuCLI 暴露了以下命令完整清单与用法见 docs/advanced-usage/builds.mdbuilds:cancel app # 取消某个应用正在运行的构建 builds:info app build-id [--format json] # 查看单个构建的详细信息 builds:list [app] [--format json] [--kind ...] [--status ...] # 列出构建不带 app 时列出全主机正在运行的构建带 app 时列出运行中 历史记录 builds:output app [build-id|current] # 显示构建输出运行中 tail 跟随已完成 cat 输出 builds:prune app [--all-apps] # 回收遗留记录并应用保留策略 builds:report [app] [flag] # 显示构建报告 builds:set [--global|app] key [value] # 设置或清除 builds 属性构建记录的生成原理从构建 ID 到磁盘记录每一次部署开始时Dokku 都会生成一个可排序的 base36 ULID 风格 ID即DOKKU_BUILD_ID。根据 plugins/builds/builds.go 中GenerateBuildID()的实现该 ID 由两部分拼接而成前 8 位毫秒时间戳的 base36 编码保证可排序时间越早 ID 越小后 6 位密码学随机数crypto/rand确保同一毫秒内的唯一性若随机源不可用则回退为时间衍生的噪声。因此每个构建 ID 恒为14 位仅含0-9和小写a-z。TestGenerateBuildIDUniquenessAndShape测试见 plugins/builds/builds_test.go验证了 200 次采样中 ID 长度、字符集与唯一性均符合预期。触发链路记录开始与结束构建记录的开始/结束由plugins/common/functions中两个关键的 bash 函数驱动见 plugins/common/functionsdokku_setup_build_capture app source在构建开始时被调用例如 plugins/git/receive-app 传入git-hookplugins/git/internal-functions 分别传入git:from-archive、git:load-image、git:from-image、git:sync。它依次完成若DOKKU_BUILD_ID尚未生成调用plugn trigger builds-generate-id获取新 ID失败时回退为pid-${DOKKU_PID}创建$DOKKU_LIB_ROOT/data/builds/$APP/目录并清空build-id.log日志文件调用plugn trigger builds-record-start写入初始记录statusrunning通过exec (tee -a $LOG (logger -i -t dokku-${DOKKU_BUILD_ID}))将构建的 stdout/stderr同时写入磁盘日志文件与 syslog标签为dokku-build-id。release_app_deploy_lock app exit_code在部署结束时无论成败调用plugn trigger builds-record-finalize把记录的最终状态、退出码与结束时间写回磁盘。触发器的 Go 实现位于 plugins/builds/triggers.goTriggerBuildsRecordStart在获取部署锁时持久化初始记录TriggerBuildsRecordFinalize则幂等地写入终态——如果记录已经处于终态例如被builds:cancel先行终结后续的 finalize 调用不会覆盖而是直接触发一次PruneAppBuilds清理TestRecordStartFinalizeIdempotency测试验证了 cancel 状态优先于后到的非零退出码。部署锁与构建记录的联动每个应用的部署互斥由.deploy.lock文件保证位于$DOKKU_LIB_ROOT/data/apps/app/.deploy.lock并且锁文件内容就是当前构建 ID见 plugins/common/functions 中echo ${DOKKU_BUILD_ID:-$DOKKU_PID} $LOCK_FILE。这一设计让builds:cancel、builds:output current等命令能够通过读取锁文件快速定位当前正在进行的构建无需额外维护状态。记录存储与 Schema对每次构建Dokku 会在磁盘上写入两个文件文件路径内容结构化记录$DOKKU_LIB_ROOT/data/builds/app/build-id.json构建元数据 JSON构建日志$DOKKU_LIB_ROOT/data/builds/app/build-id.log部署过程的 stdout/stderr磁盘日志文件是持久化的权威来源durable source of truth即使 journald 已经轮转清除了旧条目builds:output依然可以直接读取该文件。输出同时也被打上dokku-build-id的 syslog 标签因此journalctl -t dokku-build-id依然可用。一个典型的记录 JSON 如下摘自 docs/advanced-usage/builds.md{ id: 01j8c4xv7bk5w3, app: myapp, kind: build, pid: 12345, started_at: 2026-04-30T13:50:00Z, finished_at: 2026-04-30T13:51:14Z, status: succeeded, source: git-hook, exit_code: 0 }对应的 Go 结构体Build定义在 plugins/builds/builds.goWriteBuild采用写临时文件再原子 rename的方式落盘避免读到半截记录ReadBuild读取并反序列化。TestWriteAndReadBuildRoundTrip与TestBuildJSONShape测试确认运行中的记录不包含finished_at与exit_code字段Go 的omitempty且枚举以字符串形式序列化。关键字段语义kindbuild 还是 deploykind区分两类工作定义见 plugins/builds/builds.go 中的BuildKind与BuildSource.DefaultKind()build产生新镜像的路径包括git push、全部git:*命令git:sync、git:from-archive、git:from-image、git:load-image以及ps:rebuilddeploy对已有镜像的重新部署包括ps:restart、ps:start、dokku deploy、config:setsource 为config-redeploy。TestBuildSourceDefaultKind测试对全部已知 source 到 kind 的映射做了穷举验证。status五个取值一个特殊磁盘上持久化的状态只有四种BuildStatus枚举running部署进行中succeeded退出码为 0failed退出码非 0canceled被builds:cancel主动终结。第五个取值abandoned是只在读取时计算的显示值永远不会被持久化当一条记录的状态为running但其 PID 已不存在进程死亡时Build.DisplayStatus()会将其呈现为abandoned。判断依据是CheckPIDAlive——在 Unix 上通过kill(pid, 0)探测进程是否存在其中ESRCH视为已死、EPERM存在但无权发信号视为存活。TestBuildStatusValid明确断言abandoned不可持久化TestDisplayStatusComputesAbandoned验证了死 PID 的 running 记录会显示为 abandoned。source构建的发起者source记录的是触发这次部署的用户命令或内部触发器已注册的取值包括见 plugins/builds/builds.gogit-hook、ps:rebuild、ps:restart、ps:start、deploy、config-redeploy、git:sync、git:from-archive、git:from-image、git:load-image以及兜底的unknown。当TriggerBuildsRecordStart收到未注册的 source 字符串时会记录告警并将其规范化为unknownTestRecordStartUnknownSourceCoercedToUnknown覆盖此路径。列出构建记录builds:list不带 app 参数时builds:list展示宿主机上所有应用当前正在运行的构建见 plugins/builds/subcommands.go 中commandListAllRunning它会遍历全部应用、过滤出running且 PID 存活的有效记录dokku builds:list Currently running builds App Build ID Kind PID Source Started myapp 01j8c4xv7bk5w3 build 12345 git-hook 2026-04-30T13:50:00Z带 app 参数时builds:list app返回该应用的运行中构建加上最近的历史记录数量受保留策略约束排序规则为运行中的在前其余按开始时间倒序dokku builds:list myapp支持按类型与状态过滤dokku builds:list myapp --kind build dokku builds:list myapp --status running dokku builds:list --format json--kind的合法值为build、deploy--status的合法值包括running、succeeded、failed、canceled、abandoned五种注意此处过滤用的是显示状态因此可以按abandoned过滤。传入非法值会直接报错tests/unit/builds.bats中(builds:list --kindinvalid) fails with a usage error等测试用例对此有覆盖。当无 app 且无运行中构建时输出No builds currently running当某应用从未部署过时输出No builds recorded for this app。查看单个构建builds:infobuilds:info输出单条构建的完整元数据dokku builds:info myapp 01j8c4xv7bk5w3 Build 01j8c4xv7bk5w3 Build ID: 01j8c4xv7bk5w3 App: myapp Kind: build Status: succeeded PID: 12345 Source: git-hook Started: 2026-04-30T13:50:00Z Finished: 2026-04-30T13:51:14Z Duration: 1m14s Exit Code: 0 Log: /var/lib/dokku/data/builds/myapp/01j8c4xv7bk5w3.log其中Duration由源码中的Build.Duration()计算已完成的构建为FinishedAt - StartedAt按秒取整仍在进行的构建则为Now - StartedAt。Log行给出该构建日志文件的磁盘绝对路径。JSON 输出--format json在相同字段之外还附带了计算得到的log_path、display_status与durationdokku builds:info myapp 01j8c4xv7bk5w3 --format json | jq .jq未安装时可直接阅读原始 JSON。若构建 ID 不存在命令会以非零状态返回No build record found for myapp/id见(builds:info) returns non-zero for a missing build-id测试。流式查看构建输出builds:output# 运行中的构建tail -f 实时跟随已完成的构建cat 输出全文 dokku builds:output myapp 01j8c4xv7bk5w3 # 使用关键字 current 解析出当前正在进行的构建读取 .deploy.lock dokku builds:output myapp current源码实现CommandOutput见 plugins/builds/subcommands.go的具体行为未指定 build-id 或使用current时读取该应用的.deploy.lock解析出当前构建 ID若无锁文件则提示App not currently deploying若磁盘日志文件存在且记录状态为running且 PID 存活则执行tail -n 1000 -f实时跟随否则执行cat全文输出若日志文件缺失例如该构建发生在 builds 插件安装之前则回退到journalctl -n 1000 -a -o cat SYSLOG_IDENTIFIERdokku-build-id若系统无journalctl则报错提示。这一设计保证了新构建看磁盘、老构建看 journald的无缝兼容。测试(builds:output) cats the log file for a finished build验证了已完成构建的日志输出行为。取消正在运行的构建builds:canceldokku builds:cancel myappbuilds:cancel的执行流程CommandCancel如下读取该应用的.deploy.lock若不存在则提示App not currently deploying并正常退出根据锁文件中的 build-id 读取构建记录若记录已不存在则删除残留的锁文件若记录已处于非running状态则不发送任何信号记录保持原样提示no longer running若 PID 已死亡则将记录终结为failed退出码-1而非canceled若进程仍在运行则向该构建的进程组发送SIGQUIT见killProcessGroup先syscall.Getpgid(pid)取进程组再syscall.Kill(-pgid, SIGQUIT)随后将记录终结为canceled最后移除锁文件。从源码注释可以推断设计意图先到先得——如果builds:cancel先终结了记录那么随后进程退出时触发的builds-record-finalize会因记录已处于终态而保持canceled不变幂等路径反之如果进程已经退出则 cancel 只做收尾标记为 failed避免记录永远停留在running。(builds:cancel) refuses to cancel a record whose status is not running与(builds:cancel) marks an abandoned record as failed instead of canceled两个测试分别覆盖了这两种分支。保留策略按数量而非按时间清理构建记录的清理按条数而非年龄。默认保留每个应用 20 条记录常量DefaultRetention 20见 plugins/builds/builds.go。无论记录有多少正在进行的部署live in-flight永远不会被清理。保留值的解析遵循三级级联ResolveRetention应用级覆盖 → 全局覆盖 → 默认值 20。任何一级的值如果不是正整数都会打印告警并回退到下一级。TestResolveRetentionCascade测试覆盖了无覆盖、仅有全局、应用优先、非法值回退、零值回退五种场景。设置与清除保留值builds:set设置单应用的保留值dokku builds:set myapp retention 50设置全局默认值dokku builds:set --global retention 10清除覆盖回退到全局值再回退到默认值 20dokku builds:set myapp retentionbuilds:set的参数校验见 plugins/builds/set.go严格要求retention必须是正整数且不小于 1MinimumRetention 1传入0、负数或非数字都会直接报错对应测试(builds:set) rejects non-positive-integer values。目前retention是唯一可设置的属性。手动清理builds:prune每次部署结束时TriggerBuildsRecordFinalize都会自动调用PruneAppBuilds。builds:prune手动触发同一套逻辑适合在调低保留值之后或宿主机重启之后清理残留dokku builds:prune myapp dokku builds:prune --all-appsPruneAppBuilds的完整流程见 plugins/builds/builds.go回收遗留记录ReapAbandonedBuilds把所有statusrunning但 PID 已死的记录终结为failed退出码-1结束时间为当前时刻TestReapAbandonedBuilds验证了这一行为裁剪超限记录计算保留值后按开始时间倒序保留最近 N 条终态记录超出部分连同其.json与.log文件一并删除removeBuildFiles始终保护存活记录statusrunning且 PID 存活的记录即使数量超限也不会被删除——TestPruneAppBuildsRespectsRetentionAndProtectsLive测试在保留值为 2、存在 1 条 live 记录和 5 条终态记录时断言清理后恰好剩余 3 条live 2。构建报告builds:reportdokku builds:report dokku builds:report myapp dokku builds:report myapp --build-statusbuilds:report既可用于审计最近一次构建的状态也可用于查看保留策略的生效值实现见 plugins/builds/report.go--global作用域下只暴露保留相关 flag。可用 flag 分为两组保留相关 flag可通过builds:set管理Flag含义--builds-retention该应用的保留覆盖值未设置则为空--builds-global-retention全局保留覆盖值未设置则为空--builds-computed-retention该应用最终生效的保留值三级级联解析后的整数只读 flag最近一次构建的派生元数据不可通过builds:set设置Flag含义--build-id最近一次构建的唯一 ID--build-kind记录类型build或deploy--build-status显示状态running/succeeded/failed/canceled/abandoned读取时计算--build-source触发来源如git-hook、ps:rebuild、ps:restart--build-pid构建进程的 PID--build-started-at构建开始时间UNIX 时间戳/ISO 格式--build-finished-at构建结束时间未结束时为空--build-exit-code构建进程退出码未结束时为空需要注意两点--build-status返回显示状态一条 PID 已死的 running 记录报告显示abandoned而非running磁盘上的原始状态只有直接读取 JSON 文件才能看到JSON 键的命名约定builds:report --format json输出的 JSON 键是 flag 名去掉--builds-前缀后的形式如retention、global-retention、computed-retention同时在0.38.x 的弃用窗口期内带builds-前缀的旧键如builds-retention也会一并输出并将在未来的大版本中移除。--build-id、--build-status等状态键本身没有插件前缀不受影响。(builds:report) emits new stripped JSON keys alongside legacy测试用jq验证了新旧键同时存在的行为。与插件生命周期的联动builds插件还与应用生命周期深度集成见 plugins/builds/triggers.goapps:destroyTriggerPostDelete会删除该应用在data/builds/app/下的全部记录与日志并销毁其属性配置测试(builds) [storage] directory is removed on apps:destroy验证应用重命名TriggerPostAppRenameSetup会把旧应用的数据目录整体重命名并将 builds 属性克隆到新应用名下随后销毁旧属性dokku安装TriggerInstall负责初始化属性存储。这些触发器均通过plugn trigger机制暴露任何第三方插件也可以在部署流程中复用builds-record-start/builds-record-finalize等触发器。运维建议与限制综合文档与源码以下几点在实际运维中值得注意依赖$DOKKU_LIB_ROOT布局所有记录与日志都存放在$DOKKU_LIB_ROOT/data/builds/app/下默认即/var/lib/dokku/data/builds/备份恢复时可将该目录纳入备份范围可参考 docs/advanced-usage/backup-recovery.md日志文件是权威来源由于磁盘日志不随 journald 轮转而丢失建议保留默认行为让日志同时写入磁盘与 syslog合理设置保留值默认 20 条/应用通常足够高频率部署的应用可通过builds:set app retention N调低以减少磁盘占用调低后记得执行dokku builds:prune app立即生效取消是尽力而为builds:cancel依赖进程组与SIGQUIT若构建进程忽略信号或已进入不可中断状态记录会被标记为canceled但实际进程可能仍在运行需结合ps检查状态以显示值为准脚本化读取状态时优先使用builds:report app --build-status或builds:list app --format json两者返回的都是包含abandoned计算的显示状态比直接解析 JSON 文件更符合运维直觉。【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考