Dokku 架构深度解析基于 Docker 与插件系统的 PaaS 内部工作原理【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku导读本文面向希望深入理解 Dokku 内部运作机制的开发者和插件贡献者系统拆解 Dokku 的高层架构、插件系统、命令解析流程、部署流水线与基于文件的状态管理机制。读完本文你将掌握dokku命令从 SSH 接收到插件执行完成的完整调用链理解 trigger触发器与 subcommand子命令两种插件通信方式并能在源码层面定位 builder、scheduler、proxy 等核心组件的实现位置为后续编写自定义插件打下基础。一、高层架构一个 Docker 驱动的 PaaS 如何组织Dokku 是一个基于 Docker 的 Platform as a ServicePaaS提供类似 Heroku 的部署体验。从 docs/development/architecture.md 的架构图可以看出整个系统的数据流是一条清晰的单向链路------------------ | User/Client | ----------------- | | SSH / git push v ----------------- | dokku binary | | (bash script) | ----------------- | | parse_args / execute_dokku_cmd v ----------------- | Plugin System | | (plugn) | ----------------- | | triggers / subcommands v ----------------- ------------------ | Core Plugins |----| Docker / K8s | | (apps, git, | | Runtime | | config, etc.) | ------------------ ------------------四个核心组件构成了整个系统dokku binary主入口脚本仓库根目录的 dokku负责认证、参数解析并把命令路由到对应插件Plugin System基于 plugn 执行触发器、发现命令Core Plugins实现 Dokku 的全部功能——应用管理apps、Git 推送git、环境变量config、构建后端builder-、调度器scheduler-、反向代理*-vhosts等Runtime默认通过scheduler-docker-local使用 Docker也可通过scheduler-k3s使用 Kubernetes。从源码看dokku 脚本的第 58 行首先加载/etc/default/dokku系统级默认配置随后在第 10 行设置DOKKU_ROOT默认~dokku并依次 source$DOKKU_ROOT/dokkurc与$DOKKU_ROOT/.dokkurc/*第 1128 行。这些配置加载完成后脚本在第 68 行引入$PLUGIN_CORE_AVAILABLE_PATH/common/functions公共函数库然后调用parse_args解析全局参数最终在第 286 行进入execute_dokku_cmd执行具体命令。这一初始化顺序正是架构图中配置加载 → 参数解析 → 命令路由的具体实现。二、源码目录与运行时目录结构2.1 源码树按插件职责组织仓库的顶层源码结构如下与 docs/development/architecture.md 中 Source Tree 一致dokku/ ├── dokku # Main CLI entry point (bash script) ├── plugins/ # All plugin source code │ ├── apps/ # App management │ ├── git/ # Git push handling │ ├── config/ # Environment variables │ ├── builder-*/ # Build backends (herokuish, pack, dockerfile, etc.) │ ├── scheduler-*/ # Deployment schedulers (docker-local, k3s) │ ├── *-vhosts/ # Proxy implementations (nginx, traefik, caddy, etc.) │ └── common/ # Shared functions and utilities ├── docs/ # Documentation (markdown) ├── debian/ # Debian packaging files ├── contrib/ # Installation scripts and helpers └── tests/ # Integration tests (bats)其中plugins/目录下每一类插件都对应架构图中的一个能力维度appsplugins/apps应用生命周期管理gitplugins/git处理git push推送configplugins/config环境变量管理builder-*构建后端仓库中实际存在builder-dockerfile、builder-herokuish、builder-lambda、builder-nixpacks、builder-pack、builder-railpack、builder-null等多个实现scheduler-*部署调度器包括scheduler-docker-local默认与scheduler-k3s*-vhosts反向代理实现包括nginx-vhosts、traefik-vhosts、caddy-vhosts、haproxy-vhosts、openresty-vhostscommonplugins/common共享函数与工具库是整个插件生态的公共依赖。2.2 运行时目录安装后的文件布局Dokku 安装后会在主机上创建如下目录结构/var/lib/dokku/ ├── core-plugins/ # Core plugin binaries (installed from source) │ ├── available/ # All available core plugins │ └── enabled/ # Symlinks to enabled plugins ├── plugins/ # Community plugins │ ├── available/ │ └── enabled/ └── data/ # Plugin data storage └── plugin/ # Per-plugin data └── app/ # Per-app properties $DOKKU_ROOT (~dokku by default) ├── app/ # Per-app data │ ├── refs/ # Git refs │ ├── HEAD # Current git HEAD │ ├── tls/ # SSL certificates │ └── ... # Other app-specific files └── .dokkurc/ # Global configuration overrides这些路径在 dokku 脚本中均有对应定义PLUGIN_PATH默认指向$DOKKU_LIB_ROOT/plugins即/var/lib/dokku/pluginsPLUGIN_AVAILABLE_PATH与PLUGIN_ENABLED_PATH分别指向其下的available/与enabled/子目录PLUGIN_CORE_PATH则指向/var/lib/dokku/core-plugins。enabled 目录中存放的是指向 available 目录的符号链接这正是插件启用/禁用机制的文件系统基础启用插件即建立 symlink禁用即移除。需要指出的是现代版本的属性数据实际存放于$DOKKU_LIB_ROOT/config/plugin/app/而非文档旧图所示的data/目录详见下文属性系统一节的源码依据二者逻辑结构一致均为插件 → 应用 → 属性的三级组织。2.3 插件目录布局统一的约定每个插件遵循一致的结构对应 docs/development/architecture.md 的 Plugin Directory Layoutplugins/plugin-name/ ├── plugin.toml # Plugin metadata (description, version) ├── commands # Help output and catch-all command handler ├── subcommands/ # Individual command implementations │ ├── default # Default command (e.g., dokku apps) │ └── command # Named commands (e.g., dokku apps:create) ├── functions # Public functions for other plugins to source ├── internal-functions # Private functions ├── triggers.go # Go-based trigger implementations ├── *.go # Additional Go code └── Makefile # Build configuration以 plugins/apps/plugin.toml 为例元数据文件内容为[plugin] description dokku core apps plugin version 0.38.27 [plugin.config]版本号在每次发布时由构建流程同步更新。注意现代核心插件大量使用 Go 编写——plugins/apps 目录下apps.go、subcommands.go、triggers.go、report.go、functions.go均为 Go 源码配合Makefile编译出可执行文件同时保留functions、internal-functions等 bash 脚本供其他插件 source。这正是Bash Go 混合设计决策的直接体现。三、插件系统架构一切皆是插件Dokku 的全部功能都通过插件实现。这种架构带来三个关键特性可扩展性Extensibility无需修改核心代码即可添加新功能松耦合Loose coupling插件通过定义良好的 trigger 通信可组合性Composabilitybuilder、scheduler、proxy 可以自由混搭。例如用户可以在同一套 Dokku 上为不同应用选择不同构建后端herokuish / pack / dockerfile也可以把代理从默认的 nginx 切换到 traefik 或 caddy——这些都由插件机制天然支持。3.1 插件间通信Trigger 触发系统插件通过由 plugn 驱动的trigger 系统通信。当一个 trigger 被触发时plugn 会执行所有已启用插件中同名的匹配脚本--------------- plugn trigger --------------- | Plugin A | ------------------- | Plugin B | | (fires event) | post-deploy | (listens for | --------------- | event) | --------------- | v --------------- | Plugin C | | (also listens)| ---------------Trigger 执行流程某个插件调用plugn trigger trigger-name [args...]plugn 在$PLUGIN_ENABLED_PATH/*/下搜索名为trigger-name的文件每个匹配的可执行脚本都会携带参数被运行Trigger 可以通过 stdout 返回数据通过退出码报告错误。关键 trigger 分类完整清单见 docs/development/plugin-triggers.mdCategoryExamplesPurposeApp lifecyclepost-create,pre-delete,post-deployReact to app eventsBuildbuilder-detect,pre-build,builder-buildControl build processDeployscheduler-deploy,check-deployManage deploymentsProxyproxy-build-config,nginx-pre-reloadConfigure reverse proxyGitgit-pre-pull,receive-appHandle git operations以仓库中实际存在的 plugins/20_events 插件为例其目录下排列着post-create、pre-delete、post-deploy、builder-detect、scheduler-deploy、receive-app等几十个 trigger 脚本文件——它们正是上述分类表中各 trigger 的真实实现与注册位置。从 docs/development/plugin-triggers.md 的说明可知trigger 本质就是可执行脚本可以使用任意语言编写只要满足两个条件可执行、具备运行时依赖。例如实现nginx-hostnametrigger 来反转部署时提供给 nginx 的主机名#!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] set -x APP$1; SUBDOMAIN$2; VHOST$3 NEW_SUBDOMAINecho $SUBDOMAIN | rev echo $NEW_SUBDOMAIN.$VHOST这个脚本名为nginx-hostname且可执行时就会在正常部署过程中被 Dokku 调用。插件可以通过实现nginx-hostname这样的 trigger 来修改输出或覆盖内部配置。3.2 从 Go 代码调用 TriggerGo 编写的插件使用common.CallPlugnTrigger函数触发事件。该函数的完整签名定义在 plugins/common/plugn.go输入结构体PlugnTriggerInput支持Args传给 trigger 的参数列表Env传给 trigger 的环境变量Stdin/StreamStdio/StreamStdout/StreamStderr输入与控制输出流行为DisableStdioBuffer禁用 stdio 缓冲PrintCommand执行前打印命令。调用示例对应文档中的代码result, err : common.CallPlugnTrigger(common.PlugnTriggerInput{ Trigger: post-deploy, Args: []string{appName, port, ip, imageTag}, })实现上CallPlugnTrigger 实际执行plugn trigger Trigger [Args...]命令并返回ExecCommandResponse包含 stdout/stderr 与退出码。若设置了PrintCommand或环境变量DOKKU_TRACE1还会把 trigger 的 stdout/stderr 逐行写入调试日志便于排查跨插件调用问题。仓库中有大量真实调用示例例如 plugins/apps/subcommands.go 在克隆应用时触发post-app-clone-setup_, err : common.CallPlugnTrigger(common.PlugnTriggerInput{ Trigger: post-app-clone-setup, Args: []string{oldAppName, newAppName}, StreamStdio: true, })四、命令执行流程一次dokku apps:list的完整旅程当用户执行ssh dokkuhost apps:list时完整调用链如下对应 docs/development/architecture.md 的 Command Flow 图User runs: ssh dokkuhost apps:list | v -------------------------------------- | dokku bash script | | 1. Source /etc/default/dokku | | 2. Set DOKKU_ROOT, PLUGIN_PATH | | 3. Source common/functions | | 4. Call parse_args() | | 5. Check user permissions | -------------------------------------- | v -------------------------------------- | execute_dokku_cmd() | | 1. Handle plugin aliases | | 2. Check for subcommands/default | | 3. Check for subcommands/cmd | | 4. Fall back to commands scripts | -------------------------------------- | v -------------------------------------- | plugins/apps/subcommands/list | | (executes the actual command) | --------------------------------------4.1 认证与权限检查命令到达后首先经过认证环节。dokku_auth()函数定义于 plugins/common/functions它调用user-authtrigger 进行权限校验。当系统中不存在任何user-authtrigger或仅存在核心20_events插件内置的实现时直接放行否则执行plugn trigger user-auth $SSH_USER $SSH_NAME $通过各插件的user-auth脚本来决定是否授权。此外dokku 脚本中还有一道身份检查非dokku用户执行命令时会通过sudo -u dokku -E -H $0 $自动降权到 dokku 用户重跑而plugin:*、ssh-keys:add、scheduler-k3s:initialize等管理类命令则要求以 root 身份运行。4.2 SSH 原始命令的处理当通过 SSH 执行环境变量SSH_ORIGINAL_COMMAND非空时dokku 会解析原始命令git-*或git:*形式的命令走 git 专用分支其余命令通过xargs -n 1切分参数后再次调用自身。这使得ssh dokkuhost apps:list与本地dokku apps:list走完全相同的命令解析路径。4.3 命令解析顺序execute_dokku_cmd()定义于 dokku按如下优先级解析命令检查$PLUGIN_ENABLED_PATH/plugin/subcommands/default默认命令如dokku apps检查$PLUGIN_ENABLED_PATH/plugin/subcommands/command具名命令如dokku apps:create遍历$PLUGIN_ENABLED_PATH/*/commands脚本作为兜底catch-all。源码中还包含两层值得注意的细节其一插件别名机制。execute_dokku_cmd开头维护了一张别名表dokku例如docker-local→scheduler-docker-local、nginx→nginx-vhosts、herokuish→builder-herokuish、k3s→scheduler-k3s、pack→builder-pack等用户输入简短名称时自动映射到完整插件名case分支dokku还会把deploy、urls、report等命令路由到00_dokku-standard插件。其二退出码约定。兜底的commands脚本以退出码DOKKU_NOT_IMPLEMENTED_EXIT10表示本插件未实现该命令dokku此时循环继续尝试下一个插件的commands脚本若所有插件都返回 10则最终输出$* is not a dokku command.并退出dokku。全局参数解析则由parse_args()plugins/common/functions完成支持--quiet抑制输出、--trace开启 bash trace 调试、--force强制删除应用对应DOKKU_APPS_FORCE_DELETE1、--app指定目标应用并设置DOKKU_APP_NAME四个全局 flag。五、部署流水线一次 git push 的四阶段旅程当用户执行git push dokkuhost:myapp时Dokku 依次执行接收Receive、构建Build、发布Release、部署Deploy四个阶段------------- ------------- ------------- ------------- | Receive |---| Build |---| Release |---| Deploy | ------------- ------------- ------------- ------------- | | | | v v v v git-hook builder-detect builder-release scheduler-deploy receive-app pre-build core-post-deploy post-extract builder-build post-deploy5.1 阶段一Receive接收Git 接收阶段处理传入的推送并准备源代码git-hook接收推送并校验分支触发receive-app源代码被解压到临时目录触发post-extract允许修改源码。该阶段对应 plugins/20_events 中的git-hook、receive-app、post-extract等 trigger 脚本。Git 相关的具体实现集中在 plugins/git含git-from-archive、git-from-directory、git-from-image等部署源处理脚本。5.2 阶段二Build构建构建阶段根据源码创建 Docker 镜像builder-detect判定构建器类型herokuish、pack、dockerfile 等pre-buildtrigger 执行构建前钩子builder-build创建 Docker 镜像post-buildtrigger 执行构建后钩子。构建器的检测与构建逻辑分散在各builder-*插件中builder-herokuish、builder-dockerfile、builder-pack、builder-nixpacks、builder-railpack、builder-lambda等均提供了builder-detect与builder-release脚本见各插件根目录。5.3 阶段三Release发布发布阶段为部署准备镜像pre-release-builder允许修改镜像builder-release在镜像中设置环境变量post-release-builder执行最终发布钩子。5.4 阶段四Deploy部署部署阶段启动容器并配置网络scheduler-deploy启动新容器check-deploy执行健康检查core-post-deploy将流量切换到新容器post-deploy执行部署后任务旧容器被 retire回收。调度器相关的核心实现位于 plugins/scheduler-docker-local默认 Docker 调度器与 plugins/scheduler-k3sKubernetes 调度器两者都提供scheduler-deploy、scheduler-detect、scheduler-stop、scheduler-run、scheduler-enter、scheduler-logs等完整 trigger 集合。六、状态管理以文件为基础透明且可调试Dokku 采用基于文件的状态系统追求简单、透明、易调试无数据库依赖任何标准 Unix 工具都能直接检查状态。6.1 属性系统Property System插件专属配置存储在属性系统中。现代版本的实际存储路径依据 plugins/common/properties.go 的getPropertyPath/getPluginConfigPath实现为$DOKKU_LIB_ROOT/config/plugin/app/property即默认/var/lib/dokku/config/plugin/app/property——每个属性就是一个普通文件值为文件内容。读写均通过辅助函数完成# Shell fn-plugin-property-write git $APP deploy-branch main fn-plugin-property-get git $APP deploy-branch// Go common.PropertyWrite(git, appName, deploy-branch, main) common.PropertyGet(git, appName, deploy-branch)plugins/common/properties.go 中的 API 远比文档示例丰富从源码可以梳理出完整的能力矩阵函数作用PropertyWrite/PropertyGet/PropertyGetDefault单值属性的读写支持默认值L490 / L108 / L177PropertyGetAll/PropertyGetAllByPrefix枚举某应用的全部属性或按前缀过滤L113 / L143PropertyExists/PropertyDelete/PropertyDestroy存在性检查、单属性删除、整应用销毁_all_表示清空整个插件配置L101 / L76 / L90PropertyClone应用克隆时把一个应用的全部属性复制到新应用L60PropertyList*列表型属性PropertyListAdd可指定索引插入、PropertyListGet、PropertyListSet、PropertyListRemove、PropertyListRemoveByPrefix、PropertyListGetByIndex、PropertyListGetByValue等L194 起PropertyMap*Map 型属性以单个 JSON 文件持久化key 可含任意字节如/、换行提供PropertyMapWrite/PropertyMapGet/PropertyMapSet/PropertyMapDelete/PropertyMapLengthL522 起CommandPropertySet命令层入口校验应用名、校验属性名合法性、支持--global全局属性L16属性文件权限统一设置为 0600属主可读写目录为 0755保证了多用户环境下的数据安全。此外MigrateConfigToPropertiesplugins/common/properties.go#L670提供了从旧版环境变量到属性系统的幂等迁移机制通过config-get/config-get-globaltrigger 读取旧配置值写入属性后调用config-unset清理旧变量并输出迁移完成今后请使用dokku plugin:set管理的提示。6.2 应用级数据应用特定数据有时存放在$DOKKU_ROOT/app/下refs/- Git 引用tls/- SSL 证书ENV- 环境变量文件容器 ID、端口映射等例如 plugins/common/functions 的get_app_container_ids就是从$DOKKU_ROOT/$APP/CONTAINER及CONTAINER.type.*系列文件中读取容器 ID 的。文档明确指出所有非 git 数据正在逐步迁移到属性系统。从源码看MigrateConfigEntry结构体plugins/common/properties.go#L649-L665与migrateLegacyEnvFilesL714正是这一迁移运动的实现载体——后者会在升级安装时触发config-migrate-envtrigger将 0.38 之前遗留的ENV文件搬入 config 属性路径。6.3 全局配置全局设置可通过以下层级配置/etc/default/dokku- 系统级默认值$DOKKU_ROOT/dokkurc- 用户级配置$DOKKU_ROOT/.dokkurc/*- 附加配置文件这三层配置的加载顺序与 dokku 脚本完全对应先 source/etc/default/dokku再 source$DOKKU_ROOT/dokkurc最后逐个 source$DOKKU_ROOT/.dokkurc/*。后加载的配置可以覆盖先加载的默认值这为运维提供了灵活的覆盖手段。七、关键设计决策DecisionRationalePlugin-based architectureEnables extensibility without modifying core code. Community plugins can add databases, caching, and other services.Bash Go hybridBash for orchestration and simple commands; Go for performance-critical operations and complex logic.Trigger systemLoose coupling between plugins. Plugins dont need to know about each other; they just fire and respond to events.File-based stateSimple, transparent, and easy to debug. No database dependency. State can be inspected with standard Unix tools.Docker as foundationLeverages Dockers container runtime, networking, and image management. Allows multiple scheduler backends.从仓库实际情况看这五项决策均有直接源码支撑插件化架构plugins/下 40 余个插件目录 plugn触发系统社区插件与核心插件同构共存Bash Go 混合主入口 dokku 与 plugins/common/functions 是 bash而 apps、config、network、ports、ps、proxy、registry、repo、scheduler、storage 等核心插件均已迁移为 Go 实现对应各插件目录下的*.go与MakefileTrigger 系统核心20_events插件集中承载了上百个 trigger 脚本插件之间通过事件解耦文件状态/var/lib/dokku/config/plugin/app/property的纯文件属性系统PropertyGetAll等函数直接用os.ReadDir枚举目录即可列出全部属性Docker 基础DOKKU_GLOBAL_BUILD_ARGS/DOKKU_GLOBAL_RUN_ARGSdokku将org.label-schema等标签注入每次 build/run同时scheduler-docker-local与scheduler-k3s的存在证明运行时后端可替换。八、延伸阅读以下文档与源码可帮助你进一步深入docs/development/plugin-creation.md - 如何创建自定义插件docs/development/plugin-triggers.md - 全部可用 trigger 的完整清单与示例docs/development/testing.md - 如何测试 Dokku 与插件对应仓库tests/下的 bats 测试docs/deployment/application-deployment.md - 面向用户的部署指南docs/deployment/builders/builder-management.md - 可用的构建后端docs/deployment/schedulers/scheduler-management.md - 可用的调度器后端源码层面建议优先阅读 dokku主入口与命令路由、plugins/common/plugn.gotrigger 调用封装与 plugins/common/properties.go属性系统这三处是理解整个架构的钥匙。【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考