- 开发工具
- 微服务
- 云原生
- CLI
【免费下载链接】tye
Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. Project Tye includes a local orchestrator to make developing microservices easier and the ability to deploy microservices to Kubernetes with minimal configuration.
导读
微服务应用常常横跨多个 Git 仓库:前端、后端、基础设施各自独立演进。Tye 通过服务配置中的include与repository两个字段,允许一个tye.yaml引用其他仓库中的tye.yaml或直接引用 Git 仓库作为依赖,从而在不合并仓库的前提下完成本地编排(tye run)与 Kubernetes 部署(tye deploy)。读完本文,你将掌握跨仓库服务的声明方式、依赖解析与"就近优先"合并规则,以及 Git 依赖克隆的目录约定与注意事项。
本文基于 docs/reference/multirepo.md 展开,并结合仓库内 ApplicationFactory.cs 源码、tye-schema.json 配置结构与 E2E 测试加以印证。
一、多仓库场景:为什么需要跨仓库编排
在真实项目中,一个完整的应用往往由多个仓库共同组成:
- 前端仓库(frontend repo),负责页面与交互;
- 后端仓库(inventory repo),负责订单/库存等业务逻辑;
- 基础设施依赖(如 redis),可能由任一仓库管理。
Tye 对这一场景的支持方式是:通过服务条目引用另一个tye.yaml,或直接引用一个 Git 仓库。这样既不必把多个仓库强行合并为一个 monorepo,也能在tye run/tye deploy时一次性把跨仓库的依赖全部拉起。
单仓库形态下的基线配置
先看一个典型的单仓库应用。假设应用由 frontend、inventory 两个服务加上 redis 组成,inventory 依赖 redis 做订单缓存:
name: multirepo-app services: - name: frontend project: ./frontend/frontend.csproj - name: inventory project: ./inventory/inventory.csproj - name: redis container: redis bindings: - port: 6793该示例即来自原文档 docs/reference/multirepo.md。注意示例中 redis 显式指定了宿主端口6793(bindings下的port),这是容器服务声明绑定端口的典型写法。
二、跨仓库引用:include 一个 tye.yaml
当 inventory 服务被拆分到独立仓库、与 frontend 仓库并排存放时,可以把上面的配置拆成两份tye.yaml,通过include字段互相引用。
frontend 仓库的 tye.yaml
# frontend's tye.yaml name: multirepo-app services: - name: frontend project: ./frontend/frontend.csproj - name: inventory include: ../inventoryRepo/tye.yamlinventory 仓库的 tye.yaml
# inventory's tye.yaml name: multirepo-app services: - name: inventory include: inventory/inventory.csproj - name: redis container: redis bindings: - port: 6793两份配置使用了相同的name: multirepo-app。这并非巧合,而是被引用的嵌套配置必须满足的硬性约束——源码 ApplicationFactory.cs 中的GetNestedConfig会校验:
if (nestedConfig.Name != rootConfig.Name) { throw new CommandException($"Nested configuration must have the same \"name\" in the tye.yaml. Root config: {rootConfig.Source}, nested config: {nestedConfig.Source}"); }也就是说,任何通过include引入的 tye.yaml,其顶层name必须与根配置完全一致,否则会抛出CommandException。这一点在组织多个仓库的配置文件时需要格外留意。
运行/部署的行为差异
配置完成后,在两个仓库分别执行命令会得到不同的结果:
- 在frontend 仓库执行
tye run或tye deploy:frontend、inventory、redis 三者会被一起运行/部署——因为 frontend 的 tye.yaml 通过include引用了 inventory 的 tye.yaml,而后者又声明了 redis; - 在inventory 仓库执行
tye run或tye deploy:只有 inventory 与 redis 会被运行/部署——该仓库的配置是独立的,不会反向拉起 frontend。
这一行为由解析阶段决定:include是一个单向引用,依赖方向从"引用方"指向"被引用方",不会产生反向闭包。底层实现中,ApplicationFactory.CreateAsync使用一个队列对配置图做广度优先展开,每遇到include就把嵌套配置压入队列继续处理(见 ApplicationFactory.cs):
else if (!string.IsNullOrEmpty(configService.Include)) { var expandedYaml = Environment.ExpandEnvironmentVariables(configService.Include); var nestedConfig = GetNestedConfig(rootConfig, Path.Combine(config.Source.DirectoryName!, expandedYaml)); queue.Enqueue((nestedConfig, new HashSet<string>())); AddToRootServices(root, dependencies, configService.Name); continue; }服务发现注入的粒度控制
需要特别注意的是:frontend 不会获得 redis 的服务发现信息注入,因为 frontend 并没有直接依赖 redis。Tye 的依赖图严格依据配置中的引用关系构建,而非"引入了某个 tye.yaml 就把里面的服务全算作依赖"。
如果 frontend 确实需要访问 redis,有两种做法:
- 在 inventory 的 tye.yaml 中把 redis 服务引用到 frontend(不现实,方向相反,仅用于展示);
- 在 frontend 自己的 tye.yaml 中直接添加对 redis 的引用,例如补一个 redis 服务条目(
container: redis+ 绑定端口),或在 frontend 的配置里直接声明对 redis 的include。
第二种做法是推荐路径:哪个服务需要依赖,就在哪个服务的配置中显式声明,依赖关系一目了然。
三、重复服务与"就近优先"合并规则
当多个嵌套配置中出现了同名的服务时,Tye 采用"first/closest wins"(第一个/最近的胜出)的广度优先(breadth-first)排序规则:越靠近根配置、越先被解析到的同名服务定义优先保留,后出现的重复定义不会覆盖它。
这一规则在源码中有明确的实现支撑。展开循环(ApplicationFactory.cs)维护了一个visited集合,用于避免同一个配置文件被重复展开:
if (!visited.Add(config.Source.FullName)) { continue; }同时在逐服务处理时(ApplicationFactory.cs),如果根应用中已存在同名服务,则只更新依赖信息、跳过服务创建:
if (root.Services.Any(s => s.Name == configService.Name)) { // Even though this service has already created a service, we still need // to update dependency information AddToRootServices(root, dependencies, configService.Name); continue; }E2E 测试 ApplicationFactoryTests.cs 中的SettingsFromFirstWins用例直接验证了这一点:测试在嵌套的 results/tye.yaml 中同时声明了include: ../worker/tye.yaml、project: results.csproj和image: redis2的 redis 服务,最终断言根应用中的 redis 服务镜像为redis2——即"先到先得"的合并语义得到保留。
同一测试文件中的DoubleNestingWorks(ApplicationFactoryTests.cs)还验证了嵌套可以多级:根配置 include 两个子 tye.yaml,子配置再 include 更深层的 worker/tye.yaml,最终 5 个服务全部正确展开,且每个服务的依赖集合(Dependencies)均按引用关系精确构建。
四、引用 Git 仓库:repository 字段
除了引用已存在的 tye.yaml,Tye 还支持直接克隆一个 Git 仓库作为依赖。将上一节的例子改写为:
# frontend's tye.yaml name: multirepo-app services: - name: frontend project: ./frontend/frontend.csproj - name: inventory repository: https://github.com/MyOrg/InventoryRepo克隆后的运行方式判定
仓库被克隆后,Tye 会按照与本地仓库相同的约定顺序来判断如何运行/部署,判定基准是克隆仓库的根目录。支持的文件类型与顺序定义在 ConfigFileFinder.cs:
private static readonly string[] FileFormats = { "tye.yaml", "tye.yml", "docker-compose.yaml", "docker-compose.yml", "*.csproj", "*.fsproj", "*.sln" };即优先级为:
tye.yaml/tye.yml;docker-compose.yaml/docker-compose.yml;*.csproj/*.fsproj;*.sln。
判定细节:对于每种格式,目录下恰好一个匹配文件时直接采用;若有多个匹配文件(如多个 csproj),则判定失败并报错More than one matching file was found in directory '...';全部格式都找不到时,报错No project project file or solution was found in directory '...'(见 ConfigFileFinder.cs)。也就是说,被克隆的仓库若想被 Tye 正确识别,根目录下应恰好存在一个 tye.yaml(首选)或单个项目/解决方案文件。
repository 字符串即 git clone 参数
repository字段的字符串原样传递给git clone命令。因此可以充分利用git clone的参数语法——例如指定分支:
repository: https://github.com/MyOrg/InventoryRepo --branch release/1.0Tye 执行的实际命令为git clone <repository> "<clonePath>"(见 ApplicationFactory.cs),所以诸如--branch、--depth、--single-branch等git clone支持的选项都可以追加在 URL 之后。示例中的--branch release/1.0即克隆release/1.0分支。
克隆目录:.tye/deps
所有 Git 依赖默认克隆到当前仓库下的.tye/deps目录,每个依赖一个子目录,子目录名即服务名(name)。源码中的默认路径为(ApplicationFactory.cs):
var path = configService.CloneDirectory ?? Path.Join(".tye", "deps"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } var clonePath = Path.Combine(rootConfig.Source.DirectoryName!, path, configService.Name);这里有两个可观察的行为:
- 若克隆目录已存在(说明之前克隆过),Tye不会重新 clone,直接复用现有目录——这类似于增量/缓存语义,二次
tye run时无需重新拉取; - 克隆位置可通过可选的
cloneDirectory字段自定义(见下文配置结构)。
从配置结构看,repository服务条目还支持cloneDirectory属性,用于指定克隆目标目录(相对根配置目录解析)。tye-schema.json中对repository定义如下(tye-schema.json):
"repository": { "properties": { "name": { "description": "The service name. Must be unique per-application.", "type": "string" }, "repository": { "description": "The repository url which will be cloned.", "type": "string" }, "cloneDirectory": { "description": "The directory to clone the repository into.", "type": "string" } }, "required": [ "name", "repository" ] }其中name与repository为必填项,cloneDirectory可选,默认即为.tye/deps。
Git 前置依赖与失败处理
仓库克隆依赖本机安装git。源码通过 GitDetector.cs 在首次需要时执行git --version检测 git 是否可用;若未安装,会抛出明确错误(ApplicationFactory.cs):
- 未安装 git:
Cannot clone repository ... because git is not installed. Please install git if you'd like to use "repository" in tye.yaml. - clone 失败(退出码非 0):
Failed to clone repository ... with exit code ...,并附带git clone的标准错误输出以辅助排查。
E2E 测试 TyeRunTests.cs 中的MultiRepo_WorksWithCloning用例即演示了通过repository字段引用远程仓库的完整配置形态。
五、多仓库编排的完整流程
综合以上机制,使用 Tye 编排跨仓库应用的完整工作流如下:
- 拆分配置:为每个仓库准备独立的
tye.yaml,所有被互相引用的配置必须使用相同的name; - 建立引用:在需要聚合的仓库中用
include: <相对路径>/tye.yaml引用其他仓库的配置文件,或直接用repository: <git url> [git clone 参数]声明 Git 依赖; - 本地运行:在聚合仓库执行
tye run,Tye 会按广度优先展开全部嵌套配置,拉起所有被引用的服务,并仅向直接依赖方注入服务发现信息; - 部署上线:执行
tye deploy将同样展开后的服务清单部署到 Kubernetes; - 按仓库独立运行:在任一被引用仓库内执行
tye run/tye deploy,只运行该仓库自身声明的服务,互不影响。
整个过程由 ApplicationFactory.CreateAsync 统一驱动:从根配置出发,借助队列做广度优先遍历,对include展开嵌套配置、对repository执行 git clone 后按文件约定寻找可解析的配置文件,再通过AddToRootServices(ApplicationFactory.cs)逐步构建各服务间的依赖集合,最终生成完整的ApplicationBuilder应用模型。
六、常见问题与注意事项
Q1:include 的 tye.yaml 与根配置 name 不一致会怎样?A:直接抛错。GetNestedConfig强制要求嵌套配置与根配置同名,请在所有相关仓库的 tye.yaml 中统一name。
Q2:多个仓库都声明了同名服务,谁生效?A:广度优先排序下"第一个/最近的"生效,先被解析的同名服务定义保留,后出现的只补充依赖信息、不覆盖服务定义(SettingsFromFirstWins测试用例即为佐证)。
Q3:repository 克隆的仓库里放什么文件?A:Tye 依次寻找tye.yaml/tye.yml→docker-compose.yaml/docker-compose.yml→ 单个csproj/fsproj→sln,全部位于克隆仓库根目录;若同格式文件不止一个或完全缺失,会报错。
Q4:git 依赖重复运行会重复 clone 吗?A:不会。克隆目录(默认.tye/deps/<服务名>)已存在时直接复用,不会重新 clone;如需强制刷新,可清理对应目录后重跑。
Q5:repository 可以加参数吗?A:可以。repository的字符串整体传给git clone,支持追加--branch、--depth等参数,例如repository: https://github.com/MyOrg/InventoryRepo --branch release/1.0。
Q6:服务发现信息怎么控制?A:Tye 按配置中的引用关系(而非 include 展开后的服务全集)注入服务发现。哪个服务依赖哪个服务,就在哪份配置里显式声明,避免"隐式全量注入"。
延伸阅读
- 完整 schema 定义:src/schema/tye-schema.json
- 多仓库解析核心实现:src/Microsoft.Tye.Core/ApplicationFactory.cs
- 克隆仓库文件类型判定:src/Microsoft.Tye.Core/ConfigFileFinder.cs
- 多仓库 E2E 测试资产:test/E2ETest/testassets/projects/multirepo
- 配置解析与校验:src/Microsoft.Tye.Core/Serialization/ConfigServiceParser.cs
- 开发工具
- 微服务
- 云原生
- CLI
【免费下载链接】tye
Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. Project Tye includes a local orchestrator to make developing microservices easier and the ability to deploy microservices to Kubernetes with minimal configuration.
相关推荐
Earthly 多仓库(Multirepo)构建实战:跨仓库引用远程 Target 与制品拷贝
Earthly 多仓库(Multirepo)构建实战:跨仓库引用远程 Target 与制品拷贝 本篇指南围绕 Earthly 官方示例 examples/mul
CI/CDDevOps构建工具Git子模块与子树完整指南:如何高效管理多仓库依赖关系
Git子模块与子树完整指南:如何高效管理多仓库依赖关系 Git子模块(submodule)与子树(subtree)是Git版本控制系统中用于管理多仓库依赖关系的
教程文档Tye多仓库项目管理:如何高效管理跨仓库的微服务架构
Tye多仓库项目管理:如何高效管理跨仓库的微服务架构 在现代微服务开发中,跨多个仓库管理分布式应用已成为常态。Tye作为微软推出的微服务开发工具,专门解决了多仓
开发工具微服务云原生CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考