如何对接 EasyPublish 和 Docker 快速发布系统
手上这套发布系统的原始痛点很朴素:公司内网不能直连生产机,改一行代码走堡垒机手动发布要十几分钟。手工步骤是「本地 dotnet publish → 打 tar 包 → 堡垒机上传 → 逐台 scp → 逐台解压 → 逐台 systemctl restart」,任何一步漏了一台机器,就是一台机器跑着旧版本没人发现。
现在这套流程是:开发机双击一个 .bat,或者改完 .dmk 直接 EasyDocker.exe --dmk xx.dmk --pipeline,剩下的交给机器。
本文讲两个项目怎么分工、怎么串起来,以及里面几个绕不过去的坑。
一、两个项目各自负责什么
先划清边界,否则对接会很乱。
| EasyDocker | EasyPublish | |
|---|---|---|
| 形态 | WPF 桌面工具(Windows 开发机) | 自托管 Web 平台(Server + Agent) |
| 解决的问题 | 「怎么把产物变成镜像」 | 「怎么把东西发到几十台机器上并执行」 |
| 核心动作 | 生成发布命令 / Dockerfile / compose,调 docker build |
制品管理、批量下发、执行 shell、收执行结果 |
| 网络方向 | 开发机 → Docker(本机或远端 Agent) | Agent 主动连 Server(出站,穿透堡垒机) |
| 边界 | 到「镜像进了私有仓库」为止 | 从「从私有仓库拉镜像」开始 |
一句话概括:
EasyDocker 把代码变成镜像并推到私有库;EasyPublish 把指令批量送到每台服务器,在那上面执行 docker 命令把新镜像跑起来。
关键设计是 EasyPublish 的 Agent 主动连 Server(出站长轮询 + 心跳),所以生产机不需要开任何入站端口,也不需要堡垒机做反向转发。这是整条链路能在内网跑通的前提。
|
|
二、EasyPublish 侧:怎么接
2.1 三个概念先搞清
- 流程(Flow):一次发布的任务编排,绑定了默认目标主机。绑哪些机器,就发布到哪些机器——这本身就是灰度手段。
- 步骤(Step):流程里的一个动作,两种类型:
command:在目标机跑 shell,可配超时和失败策略;download:把制品管理里某个版本下发到目标目录(版本填「最新」按语义版本取最高),可勾选自动解压。
- 步骤编码(code):同一流程内唯一的短名,如
build、restart-api。外部系统只认编码,不认 Id。 步骤删掉重建(Id 变了)只要编码不变,调用方一行代码都不用改。
步骤编码只能用字母、数字、_ - .,最长 64 个字符。新建时不填会自动取步骤 Id(一串数字)——别偷懒,起个有意义的名字,否则半年后没人知道 12 是什么。
主机绑定是两级的,这点容易踩:
- 流程绑的主机 = 默认目标,也是灰度手段;
- 步骤绑的主机(可选)= 该步骤只在它绑的机器上跑,没绑则回落到流程绑的主机。
典型用法:构建步骤只绑那台 docker builder,发布步骤不绑、跟着流程在各业务机上跑。
⚠️ 例外:按 stepCodes 触发时,每个被打的步骤都必须自己绑了主机,不会回落。否则「只触发构建步骤」会悄悄变成「在流程绑定的全部机器上跑这一步」。
2.2 鉴权:只用个人令牌,别用项目令牌
对接的第一个决定点是认证方式。EasyPublish 里有两套令牌,外部系统一律用个人令牌:
| 个人令牌 | 项目发布令牌 | |
|---|---|---|
| 前缀 | ep_ |
项目级 |
| 能否追溯人 | ✅ 执行记录「触发人」列显示令牌所属用户 | ❌ 认不出人 |
| 限项目 | 可限定 | 天然限单项目 |
| 有效期 | 30/90/180/365 天或永久 | — |
| 能否用于触发 | ✅ 唯一支持 | ❌ 已拒绝(trigger 直接返回 401) |
为什么不用项目令牌——执行记录里会出现「不知道是谁触发的」。发布这种事出了问题要能定位到人和变更,匿名触发是运维事故的温床。
令牌的安全设计值得抄一遍:
- 明文只在创建时显示一次,库里只存 SHA-256 哈希,服务端也拿不回明文,忘了只能重建;
- 权限 = 用户自身权限 ∩ 令牌限定的项目,令牌只能缩小权限、不能放大;
- 令牌不能用来创建新令牌,避免一个泄露的令牌不断续期。
调用时二选一:放请求体 token 字段,或放 Authorization: Bearer ep_xxx 头。
2.3 触发接口
|
|
参数口径:
flowId必填。权限与目标主机都挂在流程上,不给它定位不到东西。stepCodes可选。不传 = 跑整条流程的所有启用步骤。projectId可选。传了就校验目标确实属于该项目,传错当场报错,不会跑到别的项目上。建议传。- 响应即本次执行记录(
executionId),拿它去查结果。
触发方式会被记进执行记录的 TriggerType:manual(页面手动)/ webapi(接口)/ cron(定时)/ ai(MCP)。定时任务没有「人」这一层,TriggerBy = 0,显示为系统。
Web 界面里现成的 curl 可以直接复制:发布管理页流程/步骤上的「调用代码」、主机管理页的「调用代码」都有。
2.4 查结果:三件套
CI 里接了触发只完成一半,还得知道成没成。
|
|
另外几个运维接口,建议接进监控:
| 用途 | 位置 |
|---|---|
| 健康检查(数据库可读 + 制品目录可写) | GET /healthz |
| OpenAPI 文档 | GET /openapi/v1.json |
| 结构化日志 | 容器 stdout + data/logs/easypublish-*.log |
| 操作审计 | GET /api/audits(管理员) |
| 制品占用 | GET /api/artifacts/usage |
/healthz 检查的是数据库可读 + 制品目录可写,不是简单的「进程活着」——这两个才是发布平台真正会静默失效的地方(磁盘满导致制品写不进去,接口却一切正常)。
三、EasyDocker 侧:怎么把镜像造出来
3.1 三种构建路径
EasyDocker 是 WPF 工具,界面三个页签(基本配置 / 命令预览 / 执行)。它支持两种打包类型:
| 类型 | 项目文件 | 构建 | 基础镜像 | 启动命令 |
|---|---|---|---|---|
| .NET | .csproj |
dotnet publish → bin/<配置>/<框架>/<运行时>/easypublish |
mcr.microsoft.com/dotnet/aspnet:X.Y |
dotnet xxx.dll |
| 前端 | package.json |
npm install && npm run build → dist |
nginx:latest |
nginx -g "daemon off;" |
产物目录刻意跟 VS2022 右键发布的目录对齐(bin\Release\net8.0\linux-x64\easypublish),但末级用 easypublish 而不是 publish,跟 VS 自己的产物区分开,避免互相覆盖。
开发机不必人人装 Docker Desktop。这是 EasyDocker 最值钱的一个设计:构建方式有两种。
- 本机构建:直接调本机
docker; - 远端 EasyAgent:把构建上下文打成 zip 上传给一台装了 Docker 的机器,那边跑
docker build,输出实时回传到「执行」页。
关键点:远端模式下发布仍然在本机执行,
build / run / composeUp / composeDown四个步骤才走 Agent。因为dotnet publish需要本机的 SDK 和源码,没必要搬过去。
远端模式有个细节做得很细:整步(pre 钩子 → 步骤命令 → after 钩子)在同一个任务里顺序执行,构建上下文只上传一次。需要留在本机跑的钩子(比如打包前准备本地文件)在行首加 @local :
|
|
第一条在本机跑,第二条在 Agent 上跑。
3.2 生成的 Dockerfile 长什么样
dotnet publish 的产物直接放进镜像,镜像里不跑构建——构建发生在开发机(或 EasyAgent),镜像构建只做 COPY。好处是镜像构建极快、构建镜像不需要 SDK:
|
|
前端类型也一个思路,产物固定放进 /app,nginx.conf 不打进镜像、运行时挂载——换配置不用重建镜像:
|
|
踩坑提醒:挂载的 nginx.conf 里 root 必须指向 /app,否则 404。
3.3 钩子 + 占位符:推送私有库就靠它
步骤钩子 pre / after 各 5 个步骤(执行发布 / 构建镜像 / 运行容器 / Compose 启动 / Compose 停止),执行顺序是 pre 命令 → 步骤命令 → after 命令,任一环节退出码非 0 即报错并中止。
支持的占位符:
|
|
推送私有库的标准姿势(勾选了「镜像名带仓库前缀」时 {image} 本身就是全名,省掉 tag):
|
|
docker rmi 建议加 -f 和 || echo ...:容器还在运行时(docker run --rm -d)不加 -f 会因镜像被占用而失败,而清理失败没必要让整步报错,把发布搞挂。
3.4 CLI:给脚本用的
GUI 是给人用的,CLI 是给流水线用的:
|
|
步骤名可简写:p b r up down,all / pipeline = 发布+构建。
退出码有约定:0 成功、1 步骤没有可执行命令、2 参数错误、3 配置文件读取失败,其他值是失败命令的真实退出码。CI 里据此判断。
⚠️ 程序是 WinExe,PowerShell / CMD 不会自动等待,必须用 start /wait 或 Start-Process -Wait:
|
|
另外一个经常被忽略的点:.dmk 里的路径(CsprojPath / VersionFilePath / DockerfilePath / ComposeFilePath)按 .dmk 所在目录存成相对路径写盘(分隔符统一 /,跨盘符保持绝对路径),读取时还原。所以把 .dmk 和项目一起提交进仓库后,换台机器、换个目录(含 Linux 容器)都能直接用。
四、怎么把两边串起来
到这里素材齐了。对接的核心思路是:
让 EasyDocker 负责「造镜像 + 推私有库」,让 EasyPublish 负责「在目标机下拉镜像 + 重启服务」,两者之间用私有仓库做交接,用 trigger 接口做衔接。
4.1 统一镜像命名
这是对接的第一前提。EasyDocker 的仓库前缀和 EasyPublish 拉镜像时用的地址必须是同一个字符串。
建议在两个项目里用同一套命名规范:
|
|
EasyDocker 侧配置:
| 配置项 | 值 |
|---|---|
| 仓库前缀 | ks-dockerhub.example.com:9999/app/ |
| 镜像名带仓库前缀 | ✅ 勾选 |
| 同步 tag latest | ✅ 勾选 |
| FROM 前缀 | ks-dockerhub.example.com:9999/library/ |
注意 FROM 前缀 会把原镜像的域名去掉再拼前缀:nginx:latest → 前缀/library/nginx:latest、mcr.microsoft.com/dotnet/aspnet:8.0 → 前缀/dotnet/aspnet:8.0。留空则 FROM 保持原样,交给 Docker 的 registry mirror。内网私有库场景建议填上,否则构建时要出公网拉基础镜像。
4.2 一个可用的完整流程
生产机上跑的是 compose,所以 EasyPublish 那边只需两步:拉镜像 + 起服务。
EasyPublish 的流程编排:
| 顺序 | 编码 | 类型 | 内容 | 绑主机 |
|---|---|---|---|---|
| 1 | pull |
command | docker compose -f /apps/myapp/docker-compose.yml pull |
不绑(跟流程) |
| 2 | up |
command | docker compose -f /apps/myapp/docker-compose.yml up -d |
不绑 |
| 3 | check |
command | docker compose -f /apps/myapp/docker-compose.yml ps |
不绑 |
/apps/myapp/docker-compose.yml(部署机上,由 EasyDocker 的「生成到文件 / 另存为」产出后提交进运维仓库):
|
|
⚠️ CPU 限制的键名是
cpus,写成cpu新版 Compose 会直接报additional properties 'cpu' not allowed(那是老的 swarm 写法)。
⚠️ compose 里用latest标签才能让部署机docker compose pull总拉最新;取消勾选「image 用 latest 标签」就钉死在镜像:版本号上,这时必须配合「同步 tag latest」一起用,否则镜像里没有 latest 标签。
EasyDocker 的 after 钩子(构建镜像步骤):
|
|
这样开发机上执行一次「发布并构建镜像」,整条链路就跑完了:编译 → 打镜像 → 推私有库 → 通知 EasyPublish → 所有目标机拉新镜像并重启 → 检查状态。
4.3 直接用 MCP 对接(更推荐)
如果只是想在发布环节少写脚本,EasyPublish 内置了 MCP 端点 /mcp(Streamable HTTP),接进 Claude Code、VS Code、Copilot 这类客户端就能直接对话式操作。
|
|
可用工具:
| 工具 | 作用 |
|---|---|
list_flows |
可见流程:Id、项目、绑定主机数、步骤数、定时配置 |
list_steps |
流程的步骤:编码 code、类型、命令/下载目标、绑定主机、超时 |
list_executions |
某流程最近的执行记录(拿 executionId) |
get_execution |
某次执行「步骤 × 主机」的结果汇总,失败任务带 stderr 末尾 |
tail_execution_logs |
增量拉行级实时输出(游标 after → 返回 maxId) |
trigger_release |
触发发布(写操作):只传 flowId = 整条流程,传 stepCodes = 只跑这几步 |
设计上有两条红线值得注意:
- 只读工具随便调,写操作只放开了「触发发布」。「中止执行」会真的杀掉目标机上正在跑的进程,不开放给 AI。
trigger_release走的是与 HTTP 接口完全相同的校验与下发路径(同一个ResolveStepsByCodes),触发方式记作ai。口径统一是为了避免「同一个 stepCodes,接口报 404、AI 那边报 400」这种漂移。
排查发布失败的标准路径:list_flows → list_executions → get_execution(含失败 stderr,通常够定位)→ 需要时 tail_execution_logs(首次 after=0,之后用返回的 maxId 续拉)。
4.4 用生产经验反推的几条纪律
对接完之后踩出来的,直接抄:
- 触发必须能追溯到人。 个人令牌 + 限项目 + 设有效期,别用永久令牌。
- 耗时步骤单独配超时。 默认 30 分钟,勾「不限时」则一直等。不配的话命令卡死时执行记录会一直挂在「执行中」。
- 给每个步骤起有意义的编码。
build/pull/up/check,而不是1213。 /healthz接进监控。 它查的是数据库可读 + 制品目录可写,能提前发现磁盘满。./data定期备份。 数据库 + 制品全在里面,升级镜像前先备份。- 生产环境改
Jwt:Key。 强随机值,别用默认的。 - 反向代理给
/mcp关响应缓冲、放开长连接超时。 否则流式响应会被攒着不发。
五、几个值得记的实现细节
5.1 「结束执行」怎么真的杀掉进程
这是个绕弯的设计,值得单独讲。
痛点:Agent 的 poll 循环是串行的(while(true) 里 await ExecuteTaskAsync(...))。它正卡在某条命令里的时候,根本不会去拉新任务。所以「中止」这条指令不能走任务队列——等它有空去拉的时候,那条命令早就跑完了。
解法是走心跳这条并发通道:
- Agent 每次心跳上报
RunningTaskId(当前正在执行的任务 Id,0= 空闲); - 「结束执行」把该执行下未完成的任务标成
aborted(不是failed,两者语义不同); - 下一次心跳(间隔 1 秒)时,Server 发现这个 Id 已是
aborted,在回执里返回abortTaskIds; - Agent 收到后取消该任务的
CancellationTokenSource,下游ct.Register(...)立刻杀掉整个进程树; - Agent 照常上报结果,Server 保留
aborted状态不被迟到的结果改写,并记一条「已被人工结束」的日志。
杀进程树这一点很关键:只 kill 掉 bash 是不够的,它派生的子进程(mvn、dotnet、nginx -s reload 等)会变成孤儿继续跑,既占资源又可能继续改目标机上的文件。
服务端看门狗(TaskTimeoutService)判超时把任务标成 failed 时,走的也是同一条通道——那正是「命令卡住不返回」最该被杀掉的场景。
覆盖不到的情况:主机离线。 指令送不达,那边的进程会按自己的节奏跑完。界面上的确认文案对此有明确提示,这点做得很诚实。
同一套机制也支撑了「强制更新 Agent」:先把该主机的发布任务标成 aborted,目标机上的进程被杀掉、poll 循环腾出来,排在后面的更新命令才能立刻被领走——否则它会一直排在那条可能跑几十分钟的构建后面。
5.2 Agent 身份:不写配置的 HostId
Agent 的身份取自机器的 product_uuid(读不到则回退 /etc/machine-id),配置文件里不需要 HostId / Token:
|
|
就这一个必填项。主机行由 Server 在首次注册时按机器标识自动创建/匹配。这么做的好处:同一份安装脚本装到多台机器,product_uuid 不同,各自主册成独立主机,不会互相顶替。用 machine-id 则会在虚拟机克隆后冲突,所以才优先 product_uuid。
5.3 Agent 是自包含单文件,不是 AOT
EasyPublish 和 EasyAgent 都面临同一个选择:目标机要不要装 .NET 运行时。
| 裁剪单文件(两个项目都用的方案) | Native AOT | |
|---|---|---|
| 目标机需装 .NET | 否(运行时打包进去) | 否 |
| 跨操作系统发布 | ✅ 可以 | ❌ 报 Cross-OS native compilation is not supported. |
| 本机额外工具链 | 不需要 | 需要原生链接器(Windows→MSVC,Linux→clang/gcc) |
| 启动速度 / 内存 | 较好 | 更好 |
| 反射(含 JSON) | 受限,需源生成规避 | 基本不可用 |
选了裁剪单文件,理由是能在 Windows 上直接出 linux-x64 产物——不需要额外的 C++ 工具链,也不需要在 CI 里搞原生构建环境。代价是必须处理裁剪对反射的限制。
EasyPublish Agent 的关键配置:
|
|
裁剪 + 压缩后从 ~39MB 降到 ~14MB。InvariantGlobalization 让二进制不依赖 ICU,跨平台部署免装 ICU 库。
裁剪的硬约束(改代码前必须知道):
- JSON 全部走源生成:所有请求/响应/输出模型都登记在
Json/AppJsonContext.cs。新增接口出入参时,必须同步加[JsonSerializable],否则运行期直接抛异常; - 已设
JsonSerializerIsReflectionEnabledByDefault=false,反射式 JSON 回退被关闭; - 因此不能用匿名类型(
new { ... })做响应体,也不能用Results.Json(obj)这种带RequiresDynamicCode的重载,要用接收JsonTypeInfo的重载:Results.Json(dto, AppJsonContext.Default.ErrorResponse)。
EasyAgent 那边踩过同样的坑,README 里专门写了一段「改代码前先看」,说明这个坑是真实踩过的。
5.4 Agent 内存开销的坑
Agent 常驻在业务机上,一次大构建的输出可能是几百 MB。EasyPublish 的 CommandExecutor 做了上限控制:
|
|
全量攒在 StringBuilder 里会让 Agent 自己把机器内存吃光(还会连带触发 OOM Killer)。超限后结果里的 stdout/stderr 会标记 Truncated,完整输出仍可在逐行日志里看到——实时日志是逐行上报的,不受这个上限影响。
另外可以用 MemoryMax 给 Agent 加 cgroup v2 硬上限(systemd 244+),超了内核杀掉进程,由 Restart=always 立刻拉起:
|
|
⚠️ 别设太小。.NET 自包含单文件自身常驻通常 30~60MB,50 偏紧,建议先观察再留 1.5 倍余量。设太小会让 Agent 频繁被 OOM 杀掉重启,表现为反复掉线——这个症状很容易被误判成网络问题。
5.5 前端产物提交进 git,以及为什么只留 3 个文件
EasyPublish 的前端产物(src/EasyPublish.Server/wwwroot/)是提交进 git 的,目的是克隆后无需先构建即可 dotnet run 看页面。
但产物带内容哈希、每次构建都换一批文件名,如果按默认拆包粒度,一次前端改动会产生几十个文件的增删,diff 噪音无法忍受。所以 vite.config.js 刻意压缩了拆包粒度:
|
|
配合「页面不再按路由拆包」(main.js 里静态引入),产物固定只有 3 个文件:
assets/index-*.js—— 业务代码assets/vendor-*.js—— 第三方依赖(文件名稳定,可长期缓存)assets/index-*.css—— 全部样式
结果是:只改业务代码时,只动 index.js 和 css 两个文件;vendor 文件名不变,浏览器不用整包重下。代价是首屏会带上其它页面的代码——内部工具,可接受。
镜像构建不受影响:Dockerfile 里由 node:22 阶段从 web/ 源码现构建,且 .dockerignore 排除了仓库里的产物,避免本机残留混进镜像。
5.6 前后端打包进一个 dll
vite.config.js 的 outDir 直接指向 Server 的 wwwroot,所以一次 npm run build 就完成前端往服务端资产的输出,部署只有一个 Server 容器,没有独立的 nginx 容器。
Server 的 csproj 里对应地把 wwwroot/** 标记为 CopyToOutputDirectory:
|
|
注意用 Update 而非 Include——Web SDK 已默认把 wwwroot 作为 Content 项,重复 Include 会报 NETSDK1022。
5.7 版本号:两条独立的版本线
EasyPublish 里 Server 和 Agent 的发版节奏不同,刻意不绑在一起:
| 版本写在哪 | 影响 | |
|---|---|---|
| Server + Docker 镜像标签 | 根目录 Directory.Build.props 的 <Version> |
Server 程序集版本;docker-maker.bat 从这里解析镜像标签 |
| Agent | src/EasyPublish.Agent/EasyPublish.Agent.csproj 顶部的 <Version> |
Agent 程序集版本(运行时 ReadOwnVersion() 读它,用于自更新比对) |
为什么不共用:Agent 装在业务主机上、靠自更新升级,Server 却天天改。绑同一个号的话,每次只改 Server 也会让所有主机以为自己该升级,白白重下一次二进制。
实现上不需要任何额外配置——MSBuild 的求值顺序是 Directory.Build.props 先加载、项目自己的 PropertyGroup 后求值,所以 Agent csproj 里那行 <Version> 会自然覆盖根目录的值。
Server 对外宣告的「最新 Agent 版本」取自 agents/easylp-agent.version(Agent 发布时自动写入),与实际分发的二进制严格一致。appsettings.json 的 Agent:LatestVersion 留空即可,只有需要临时钉版本时才填。
⚠️ 改完 Agent 的
<Version>必须重新dotnet publish一次,否则agents/easylp-agent.version还是旧值,Server 会宣告错误的版本——然后所有主机会错误地认为自己需要升级(或者该升级的不升)。
5.8 执行日志与审计
- 写操作自动入库(
/api/audits、界面「操作审计」),保留天数由Audit:RetentionDays控制(默认 180 天,0 = 永久); - Agent 心跳与查询类请求不入库,否则审计表会被心跳写爆;
- 清空审计日志本身也会被记录;
- MCP 调用不带
/api/前缀,因此不参与RequestAuditMiddleware的审计入库——将来开放写工具时,需要在工具内部显式记一条审计。
日志同时输出到容器 stdout 和 data/logs/(按天滚动,默认保留 14 个文件)。容器里 stdout 日志一重启就没了,必须落盘到挂载卷才可能事后排查。用的是 Serilog 核心 + Serilog.Extensions.Logging 接管 ILogger,不引 Serilog.AspNetCore——少一个包,也让 RequestLogging 中间件仍然是自己写的,便于和审计日志合并处理。
六、踩坑清单
按「踩了会疼」的程度排序:
| 坑 | 症状 | 解法 |
|---|---|---|
Agent MemoryMax 设太小 |
Agent 反复掉线,误判成网络问题 | .NET 自包含单文件常驻 30~60MB,至少留 1.5 倍余量 |
按 stepCodes 触发时步骤没绑主机 |
「只触发构建」变成「在全部机器上跑构建」 | 按步骤触发时每个步骤必须自己绑主机 |
改 Agent <Version> 没重新 publish |
Server 宣告错误版本,主机错误升级 | 改完必须 dotnet publish 一次 |
docker rmi 没加 -f |
容器还在跑,镜像被占用导致清理失败、整步报错 | docker rmi -f ... || echo ... |
compose 里写 cpu 而不是 cpus |
additional properties 'cpu' not allowed |
新版 Compose 规范键名是 cpus |
挂载的 nginx.conf 里 root 没指 /app |
站点 404 | 前端产物固定放 /app,nginx.conf 里 root /app; |
新增 DTO 忘了加 [JsonSerializable] |
裁剪后运行期抛异常 | 所有请求/响应模型登记进 AppJsonContext |
| 用匿名类型做响应体 | 裁剪后运行期抛异常 | 用具名 DTO + Results.Json(dto, TypeInfo) |
| 耗时步骤没配超时 | 命令卡死时执行记录永远挂在「执行中」 | 编辑步骤单独配执行超时 |
反向代理没给 /mcp 关缓冲 |
流式响应被攒着不发 | 关闭响应缓冲、放开长连接超时 |
compose 用 latest 但没同步 tag latest |
部署机 pull 拉不到镜像 |
「同步 tag latest」与「image 用 latest」配合使用 |
七、总结
这套东西的设计取舍值得记住三条:
- Agent 主动连 Server。 出站连接穿透堡垒机,生产机不开入站端口,这是内网自动化的前提。
- 把「造镜像」和「发东西」拆成两个项目。 构建需要 SDK 和源码,分发需要网络可达和批量能力,绑一起只会两头都做不好。两者用私有仓库做交接,用 HTTP 接口做衔接。
- 外部系统一律用个人令牌。 认不出人的触发记录等于没有记录。
EasyDocker 侧的关键是 .dmk 的相对路径(可随仓库分发到 CI)和 --silent --log 的 CLI 模式;EasyPublish 侧的关键是步骤编码(外部系统只看编码不看 Id)和 trigger 接口的 stepCodes 精确触发。两边对齐镜像命名规范,整条链路就通了。
参考资料
- EasyPublish 源码:
D:\Github\EasyPublish(README 含完整的接口、MCP 工具与运维说明) - DockerMaker 源码:
D:\Github\DockerMaker(EasyDocker/README.md+EasyAgent/README.md) - Docker 官方文档
- Model Context Protocol
- .NET 裁剪与 AOT 部署