Dawn's Blogs

分享技术 记录成长

0%

istio 的安全体现在提供全面的安全解决方案,Istio 提供了强大的身份、强大的策略、透明的 TLS 加密,以及验证、授权和审计(AAA)工具来保护服务和数据。istio 安全性保证涉及多个组件:

  • 证书管理机构(CA)。
  • (控制平面)将安全配置分发给代理:包括认证策略、授权策略、安全命名信息。
  • Sidecar 和边缘代理作为策略执行点(Envoy),用于保证通信安全。
  • Envoy 代理扩展,用于管理遥测和审计

安全架构

证书管理

istio 使用 X.509 证书为每一个工作负载提供身份标识,istio-agent(即 pilot-agent) 与 Envoy 代理一起运行,与 istiod 一起协作来自动化的进行大规模密钥和证书轮换。

pilot-agent 通过 Unix socket 的方式在本地提供 SDS 服务供 Envoy 使用

istio 通过以下流程提供密钥和证书:

  1. istiod 提供 gRPC 服务以接受证书签名请求(CSR)。
  2. istio-agent 在启动时创建私钥和 CSR,然后将 CSR 及其凭据发送到 istiod 进行签名
  3. istiod CA 验证 CSR 中携带的凭据,成功验证后签署 CSR 以生成证书
  4. 当工作负载启动时,Envoy 通过 SDS API 向同容器内的 istio-agent 发送证书和密钥请求
  5. istio-agent 通过 Envoy SDS API 将从 istiod 收到的证书和密钥发送给 Envoy
  6. istio-agent 监控工作负载证书的过期时间。上述过程会定期重复进行证书和密钥轮换

身份供应流程

认证

Istio 提供两种类型的认证:

  • 对等认证:用于服务到服务的认证,以验证建立连接的客户端。 对于对等认证,istio 自动的将流量升级为 mTLS。
  • 请求认证:用于终端用户认证,以验证附加到请求的凭据(JWT)。对于请求认证,应用程序负责获取 JWT 凭证并将其附加到请求。

使用对等认证策略和双向 TLS 时,istio 将身份从对等认证提取到 source.principal 中。 同样,使用请求认证策略时,istio 会将 JWT 中的身份赋值给 request.auth.principal。

使用 principal 用于设置授权策略

双向 TLS

istio 可以透明的实现双向 TLS,这包括两方面的认证:

  • 客户端,根据安全命名信息检查服务器的标识, 以查看它是否是工作负载授权的运行程序。
  • 服务器端, 服务器可以根据用户身份信息,通过授权策略确定客户端可以访问哪些信息。

宽容模式 Permissive mode

双向 TLS 具有一个宽容模式,允许服务同时接受纯文本流量和双向 TLS 流量。

安全命名

服务器身份(Server identity)被编码在证书里, 但服务名称(service name)通过服务发现或 DNS 被检索。 安全命名信息将服务器身份映射到服务名称。服务器身份与服务名称的映射,就表示授权服务器身份运行这个服务。

控制面将安全命名映射信息安全的分发到每一个代理,使得客户端可以检查服务器是否合法

认证策略

可以通过 PeerAuthentication 和 RequestAuthentication 来分别设置对等认证和请求认证策略。

授权

istio 通过 AuthorizationPolicy CRD 对授权策略进行配置,对服务器端 Envoy 代理的入站流量实施访问控制

授权策略支持 ALLOW、DENY、CUSTOM 操作,通过 CUSTOM、DENY、ALLOW 的顺序依次检查授权策略

授权策略优先级

认证

在 Kubernetes API 服务器中,使用 ServiceAcount 进行认证。在 Pod 中如果没有指定 ServiceAcount 则会使用默认的 ServcieAcount。

在 Pod 中,认证 token 被存储在 /var/run/secrets/kubernetes.io/serviceaccount/token 文件中,这个文件通过加密卷挂载进每个容器的文件系统中。

ServiceAcount 资源

ServiceAcount 在 K8s 中也是一种资源,在每一个 namespace 中都有一个默认的 ServiceAcount,Pod 使用在同一命名空间中的 ServiceAcount 进行认证。spec.serviceAccountName 字段可以指定 ServiceAcount。

API 服务器在收到 token 时会认证 token 属于哪一个 ServiceAcount,进而使用 RBAC 插件进行授权

授权

Kubernetes 使用 RBAC 插件进行授权,RBAC 插件检查用户的动作是否可以被执行。

动词

RBAC 插件中的动作与 Restful HTTP 请求方法对应,动作包括 get、list、create、update、patch、delete、deletecollection。

image-20231007122832271

RBAC 动作针对某一类资源,也可以指定某个资源实例,甚至是非资源的 URL 路径(因为并不是 API 服务器对外暴露的每个路径都映射到一个资源,如 /api 路径本身或服务器健康信息路径 /healthz)。

RBAC 资源

RBAC 授权规则是通过四种资源来进行配置的,可以分为两组:

  • Role 和 ClusterRole:指定可以在资源上执行哪些动词
  • RoleBinding 和 ClusterRoleBinding:将上述角色绑定到 ServiceAcount 上

前缀是否有 Cluster,区别在于:是否是集群级别的资源。

架构

Components of Kubernetes

  • 控制平面(Control Plane)组件:控制平面组件会为集群做出全局决策,比如资源的调度、 以及检测和响应集群事件。

    • kube-apiserver:API 服务器,相当于控制平面的前端负责接受请求,可以水平扩容。

    • etcd:所有集群数据的后台数据库。

    • kube-scheduler:负责 Pod 的调度决策工作。

    • kube-controller-manager:负责运行控制器进程(控制器通过 API 服务器监控集群的公共状态,并致力于将当前的状态转换为期望的状态)。从逻辑上讲, 每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。控制器包括但不限于:

      • 节点控制器:负责在节点出现故障时进行通知和响应。
      • 任务控制器:监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成。
      • 端点分片控制器:填充端点分片(EndpointSlice)对象(以提供 Service 和 Pod 之间的链接)。
      • 服务账号控制器:为新的命名空间创建默认的服务账号(ServiceAccount)。
    • cloud-controller-manager:仅运行特定于云平台的控制器。

  • Node 组件:节点组件会在每个节点上运行,负责维护运行的 Pod 并提供 Kubernetes 运行环境。

    • kubelet:在每一个节点上运行,用于管理运行在这个节点上的 Pod。

    • kube-proxy:是每一个节点上的网络代理,维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。

    • 容器运行时(Container Runtime):负责运行容器的软件。

组件间的分布式特性

组件之间的通信:在 Kubernetes 组件之间,只能通过 API 服务器进行通信,组件之间不会直接通信。

多实例保证可用性:为了保证高可用性,控制平面上的组件都是可以多实例运行的。其中,etcd 和 API 服务器都是可以多实例并行工作的。但是,同类型的调度器和控制器管理器只有一个实例起作用,其他实例均在待命。

组件运行方式:控制平面组件和 kube-proxy 都是直接在系统上运行或者作为 Pod 运行。kubelet 直接在系统上运行,把其他组件作为 Pod 运行。为了使得控制平面组件作为 Pod 运行,kubelet 也被部署在 master 上。

控制平面上的所有组件作为 Pod,在 master 节点上运行(master 节点上部署了 kubelet)。

Kubernetes 如何使用 Etcd

Etcd 是一个分布式、高可用的 kv 存储,唯一与 etcd 通信的就是 API 服务器,其他组件通过 API 服务器读写数据到 etcd 中,使用乐观锁机制进行并发写入控制。

Kubernetes API 服务器实现了乐观锁机制,所有资源都有一个 meta.resourceVersion 字段,当更新对象时,客户端返回这个值到 API 服务器,如果版本值与 etcd 不一致,则会拒绝更新。

Kubernetes 将资源的完整 Json 形式都存储在 etcd 中,etcd 是层级存储结构,kubernetes 的所有资源数据都在 etcd 的 /registry 下。

了解 API 服务器

API 服务器提供了 Restful API,用于对 Kubernetes 集群中的资源进行 CRUD 操作,并且将资源状态持久化到 etcd 中。

更改资源时 API 服务器做了什么

在 API 服务器收到一个 POST HTTP 请求后:

  • 利用认证插件认证客户端。API 服务器轮询认证插件,直到有一个插件能够认证客户端身份,包括客户端的用户名、用户 ID、用户组。
  • 通过授权插件授权客户端。授权插件根据用户名、用户 ID、用户组来决定是否授权客户端的操作,授权插件也有多个。
  • 通过准入控制插件修改资源请求。如果尝试修改、新建、删除资源,需要经过准入控制插件的验证。准入控制插件会初始化资源定义中漏配的字段为默认值,甚至修改不在请求中的相关资源,同时也会因为某些原因拒绝一个请求。
  • 持久化。之后会将资源状态的改变持久化到 etcd 中,然后给客户端一个响应。

image-20231003171103084

阅读全文 »

可观测性:为服务网格内的所有通信生成详细的遥测数据。这种遥测技术提供了服务行为的可观测性,使开发者能够排除故障、维护和优化其应用。 更好的是,它对于服务来说是透明的,不会为开发带来负担。

Istio 的遥测技术包括详细的指标分布式跟踪和完整的访问日志

指标

指标提供了一种以聚合的方式监控和理解行为的方法。

为了监控服务行为,Istio 为服务网格中所有出入网格, 以及网格内部的服务流量都生成了指标。除了监控网格中服务的行为外,监控网格本身的行为也很重要。 Istio 组件可以导出自身内部行为的指标, 以提供对网格控制平面的功能和健康情况的洞察能力。

代理级别的指标

Istio 代理级别的指标从边车代理开始,每个代理为通过它的所有流量(入站和出站)生成一组丰富的指标。

Istio 此级别的指标,就是 Envoy 生成的统计信息指标。

服务级别的指标

Istio 还提供了一组面向服务的指标。 这些指标涵盖了四个基本的服务监控需求:延迟、流量、错误和饱和情况。

默认情况下,标准 Istio 指标会导出到 Promethus 中,使用 Grafana 作为仪表盘。

控制平面指标

Istio 控制平面还提供了一组自我监控指标,这些指标容许监控 Istio 自己的行为。

分布式追踪

分布式追踪通过监控流经网格的单个请求,提供了一种监控和理解行为的方法。 追踪使网格的运维人员能够理解服务的依赖关系以及在服务网格中的延迟源

Istio 支持通过 Envoy 代理进行分布式追踪,支持多种追踪系统,包括 Zipkin、Jaeger、Datadog、LigitStep。

运维人员控制生成追踪的采样率,这允许运维人员控制网格生成追踪数据的数量和速率。

访问日志

访问日志提供了一种从单个工作负载实例的角度监控和理解行为的方法。

Istio 以一组可配置的格式,生成访问日志。

Istio 的流量管理提供了以下功能:

  • 路由规则:Istio 提供流量路由规则,可以控制服务之间的流量和 API 调用。
  • 简化服务级别属性的配置:提供熔断器、超时、重试机制,透明的为服务增加弹性
  • 可设置重要任务:可以进行 A/B 测试、canary 部署和基于百分比流量分割的分阶段部署。
  • 开箱即用的故障恢复:使得应用更健壮地应对网络或者服务故障。

虚拟服务和目标规则

虚拟服务(Virtual Service)和目标规则(Destination Rule)是 Istio 流量路由功能的核心构建模块

虚拟服务

VirtualService 旨在解耦客户端请求的地址与响应请求的目标工作负载。使用虚拟服务,可以为一个或者多个主机名(host)制定流量行为,可以使用路由规则,配置路由目标地址。

如何理解客户的请求地址与实际响应的目标工作负载的解耦

客户端将虚拟服务视为一个单一实体,将请求发送至虚拟服务(这个过程中,客户的并不知道具体如何进行路由,它只知道一个虚拟服务的地址), 然后 Envoy 根据虚拟服务规则把流量路由到不同的版本。

阅读全文 »

Istio 介绍

服务网格

服务网格(Service mesh),作为服务间通信的基础设施层。与应用部署一起,但对应用透明。应用作为服务的发起方,只需要用最简单的方式将请求发送给本地的服务网格代理,然后网格代理会进行后续的操作,如服务发现,负载均衡,最后将请求转发给目标服务。

服务网格目的是解决系统架构微服务化后的服务间通信和治理问题。服务网格由 Sidecar 节点组成,实现了数据面和控制面的解耦。服务网格的功能旨在实现以下方面:

  • 流量控制:包括两个方面:
    • 流量管理:服务发现、服务注册、负载均衡、路由等。
    • 弹性:超时、重试、熔断等。
  • 可观测性:指标、分布式追踪、日志。
  • 安全性:服务间访问控制、加密通信。

Istio 是什么

Istio 是由谷歌、IBM 和 Lyft 创建的服务网格开源实现,以透明的方式向服务架构中添加流量控制、可观测性、安全性。Istio 由一个控制平面和基于 Envoy 的数据平面组成。

服务网格与 SOA 中企业服务总线(ESB)之间的关系:

相同点:在架构中,不论是服务网格还是 ESB,对于应用来说都是透明的,应用程序不应该感知到。并且,ESB 与服务网格之间功能重合,如都提供重试、超时、熔断、服务发现、负载均衡。

不同点:

  • ESB 非常集中化的部署,而服务网格是分布式的部署,sidecar 与应用程序一起被分布式的部署在集群中。
  • ESB 非常复杂,提供了业务转换、服务编排等不属于服务网格的职责,也就是 ESB 基于复杂的专有供应商软件。

服务网格和 API 网关之间的关系:

相同点:都具有服务发现、可见性、安全、流量管理等特性(二者之间的用途有重叠)。

不同点:

  • API Gateway 主要的作用是暴露 API 给外部,API 通过调用下游的组合或者单个微服务的方式,来提供业务逻辑。
  • 而服务网格仅仅是服务之间的基础设施,不了解应用中的业务逻辑。

所以,可以将 API Gateway 和 Service Mesh 一起部署。API Gateway 负责应用逻辑,Service Mesh 负责服务发现、可见性等服务基础设施(可能会影响性能,多了本地连接的时间,但是可以通过水平扩展来弥补)。

数据平面通过代理(Envoy)拦截微服务架构集群中的所有网络流量,与每个服务一起部署,提供流量管理、可观测性等功能。

控制平面提供 API 供运维人员配置数据平面,动态的对数据平面进行配置。

基本特性

Istio 透明的为微服务之间提供了三个特性,流量管理、可观测性、安全性。

流量管理

  • 流量管理:
    • 路由规则:Istio 提供流量路由规则,可以控制服务之间的流量和 API 调用。
    • 简化服务级别属性的配置:提供熔断器、超时、重试机制,透明的为服务增加弹性
    • 可设置重要任务:可以进行 A/B 测试、canary 部署和基于百分比流量分割的分阶段部署。
    • 开箱即用的故障恢复:使得应用更健壮地应对网络或者服务故障。

可观测性

  • 可观测性:为服务网格内的所有通信生成详细的遥测数据。这种遥测技术提供了服务行为的可观测性,使开发者能够排除故障、维护和优化其应用。 更好的是,它对于服务来说是透明的,不会为开发带来负担。

Istio 的遥测技术包括详细的指标分布式跟踪和完整的访问日志

安全性

  • 安全性:包括全面的安全解决方案,Istio 提供了强大的身份、强大的策略、透明的 TLS 加密,以及验证、授权和审计(AAA)工具来保护服务和数据。
阅读全文 »

Envoy 介绍

Envoy 是由 C++ 实现的,为面向大型现代服务架构而设计的 L7 代理和通信总线。Envoy 是一个独立的进程,伴随每个应用服务运行(service mesh),所有的 Envoy 形成了一个透明的网络,每一个应用与 localhost 收发消息,而对网络拓扑结构无感知。这样做的好处:

  • 适用于任何编程语言。Envoy 可以在 Java、Go、Python 等应用程序之间形成一个网络,Envoy 弥合了它们之间的差异。
  • Envoy 可以透明地在整个基础架构上快速部署和升级。

Envoy 在云原生中可以扮演边车的角色,所有到服务的流量都通过 Envoy 代理。

img

特点

Envoy 有以下功能/特点:

  • L3/L4/L7 架构:传统的网络代理要么在 HTTP 层工作,要么在 TCP 层工作。在 HTTP 层会读取整个 HTTP 请求数据,对请求解析并查看 HTTP 头部和 URL 以决定做什么;同时也需要读取后段的整个响应数据,并将其发送给客户端。这种方法非常复杂和缓慢,解决方法就是下沉到 TCP 中去操作。在 TCP 层只读取和写入字节,并使用 IP + 端口号来决定如何处理,但是无法根据不同的 URL 代理到不同的后端。Envoy 同时支持在 3/4 和 7 层工作,以此应对各自层级的限制。
  • 顶级 HTTP/2 支持:Envoy 将 HTTP/2 视为一等公民,支持 HTTP2 和 HTTP/1.1 之间的转换。
  • 服务发现和动态配置:Envoy 可以通过 API 来实现其控制平面(典型的,Istio 就是 Envoy 的一个控制面),控制平面可以实现服务发现并通过 API 接口动态更新 Envoy(数据面)的配置。不仅如此,控制平面还可以通过 API 将配置进行分层,然后逐层更新。
  • gRPC 支持:Envoy 支持 HTTP/2,同时也支持 gRPC。
  • 特殊协议支持:Envoy 支持对特殊协议在 L7 进行嗅探,包括 MongoDB、DynamoDB 等。
  • 可观测性:Envoy 使网络透明,可以生成许多流量方面的统计数据,这是其它代理软件很难取代的地方,可以集成 Promethus 等监控方案、分布式追踪系统。

Envoy 架构

Envoy 使用单进程-多线程架构。一个主线处理与协调任务,多个 worker 线程负责监听、过滤、转发。 当一个连接被监听器接受,连接的剩余生命周期将绑定在当前 worker 线程,这使得 Envoy 近似于非阻塞。这使得 Envoy 大部分连接近似单线程运行(高度并行), 只有少量的复杂代码用于实现 worker 线程之间的协调。

所以,worker 线程数量应该配置为物理机的线程数量

Envoy 同 Nginx 一样,也采用了多进程 + 非阻塞 + 异步 IO。

默认情况下,worker 线程之间没有协调。这表示所有 worker 线程都独立的尝试从每一个监听器接受连接, 然后依赖内核在线程之间执行适当的均衡。对于大多数工作负载,内核都可以很好的均衡传入的连接。但是, 对于某些工作负载,特别是那些只有少量但非常长寿命的连接的工作负载(例如 gRPC 流), 则可能希望让 Envoy 强制平衡工作线程之间的连接。为了支持这种行为, Envoy 允许为每个监听器配置不同类型的连接均衡。

下图为 Envoy 的架构图:

img

Envoy 有四个关键组件

  • 监听器(Listener): 监听器定义了 Envoy 如何处理连接请求,一旦建立连接之后,就会将该请求传递给一组过滤器(filter)进行处理。
  • 过滤器(Filter): 过滤器是处理入站和出站流量的链式结构的一部分。在过滤器链上可以集成很多特定功能的过滤器,例如,通过集成 GZip 过滤器可以在数据发送到客户端之前压缩数据。
  • 路由(Router): 路由用来将流量转发到具体的目标实例,目标实例在 Envoy 中被定义为集群。
  • 集群(Cluster): 集群定义了流量的目标端点,同时还包括一些其他可选配置,如负载均衡策略等。

Filter

Envoy 运行着一系列 Listener,Listener 的核心就是过滤器链(FilterChain)。过滤器链中的过滤器分为两个类别:

  • 网络过滤器(L3/L4 层):是 Envoy 网络连接处理的核心,处理的是原始字节,分为 Read、Write 和 Read/Write 三类。
  • L7 过滤器:在 L3/L4 之上的过滤器,主要支持 HTTP 和 Thrift 协议。
    • HTTP 过滤器:由 HTTP connection manager 管理,专门处理 HTTP1/HTTP2/gRPC 协议,将原始字节转换成 HTTP 格式,从而对 HTTP 协议进行精准控制。
    • Thrift Proxy:用于支持 Thrift 协议。

除了过滤器链之外,还有一种过滤器叫监听器过滤器(Listener Filters),它会在过滤器链之前执行,用于操纵连接的元数据。这样做的目的是,无需更改 Envoy 的核心代码就可以方便地集成更多功能。

img

本文内容不谈 dubbo-kubernetes,因为 dubbo-kubernetes 在构建镜像时可以使用 Buildpack 来构建 OCI 镜像,所以本文来谈一谈 Buildpack。

dubboctl 在 build 镜像时可以选择 buildpack 或者 dockerfile 的方式。

OCI

随着 Kubernetes 成为了重启编排的事实标准,为了确保所有的容器运行时都都运行任何构建工具生成的镜像(而不仅仅是 Docker),OCI(Open Container Initiative)开放容器协议被提出,OCI 制定了围绕容器镜像格式(image-spec)和运行时(runtime-spec)的行业标准。给定一个 OCI 镜像,任何实现 OCI 运行时标准的容器运行时都可以使用该镜像运行容器。

Docker 镜像和 OCI 镜像有什么区别?

答案是:几乎没有区别。有一部分旧的 Docker 镜像在 OCI 规范之前就已经存在了,它们被成为 Docker v1 规范,与 Docker v2 规范是不兼容的。而 Docker v2 规范捐给了 OCI,构成了 OCI 规范的基础。

阅读全文 »

Client

dubbo-kubernetes 的命令行工具 dubboctl 用于构建项目、打包成镜像、推送至 registry、部署 kubernetes 等。

而 dubboctl 的核心在于 Client,表示 dubboctl 客户端,执行 dubboctl 的各种操作。Client 定义在 app/dubboctl/internal/dubbo/client.go 文件中,其结构如下。其中,Client 包含了 builder、pusher、deployer 等组件,用于打包镜像、推送、部署工作。

1
2
3
4
5
6
7
8
9
10
type Client struct {
repositoriesPath string // path to repositories
repositoriesURI string // repo URI (overrides repositories path)
templates *Templates // Templates management
repositories *Repositories // Repositories management
builder Builder // Builds a runnable image source
pusher Pusher // Pushes function image to a remote
deployer Deployer // Deploys or Updates a function}
KubeCtl *kube.CtlClient // Kube Client
}

初始化函数

New 用于初始化一个 Client,该函数根据配置 options 配置 Client,并且初始化 repository 和 template 管理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// New client for function management.
func New(options ...Option) *Client {
// Instantiate client with static defaults.
c := &Client{}
for _, o := range options {
o(c)
}

// Initialize sub-managers using now-fully-initialized client.
c.repositories = newRepositories(c)
c.templates = newTemplates(c)

return c
}

dubboctl 使用的默认初始化函数

在 dubboctl 使用 Client 时,又封装了一个默认的初始化函数 NewClient,用于提供完整功能的 Client,包括配置:

  • 默认的本地 repository 地址,dubbo.WithRepositoriesPath。
  • Builder 镜像构造器,dubbo.WithBuilder。
  • Pusher 用于将镜像推送至 registry,dubbo.WithPusher。
  • Deployer 用于部署应用至 kubernetes,dubbo.WithDeployer。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func NewClient(options ...dubbo.Option) (*dubbo.Client, func()) {
var (
t = newTransport(false)
c = newCredentialsProvider(config.Dir(), t)
d = newDubboDeployer()
o = []dubbo.Option{
dubbo.WithRepositoriesPath(config.RepositoriesPath()),
dubbo.WithBuilder(pack.NewBuilder()),
dubbo.WithPusher(docker.NewPusher(
docker.WithCredentialsProvider(c),
docker.WithTransport(t))),
dubbo.WithDeployer(d),
}
)
// Client is constructed with standard options plus any additional options
// which either augment or override the defaults.
client := dubbo.New(append(o, options...)...)

cleanup := func() {}
return client, cleanup
}

三层结构

在 dubboctl 创建应用时,支持自定义应用模版。而在 dubboctl 中有三层结构的概念,分别是 repository,runtime,template。

其中,repository 指的是一个仓库(可以是本地的或是远程的 git 仓库),而 runtime 指的是使用的编程语言,template 为创建应用的具体模版。

如默认仓库 default 的结构如下,有两种 runtime 分别是 go 和 java,而每一种 runtime 中又指定了一个名为 common 的模版。

image-20230920175155155

Repository Runtime Template 之间都是一对多的关系,一个 Repository 下有多个 Runtime,一个 Runtime 下有多个 Template。

Repository

Repository 被定义为存放 template 的仓库,这个仓库可以是本地的,也可以是远程的。至于 repository 是本地的还是远程的,取决于 uri,根据 uri 构造不同类型的文件系统,如上一篇文章所述,dubbo-kubernetes 抽象出了多种不同的文件系统。

若 uri 以 file:// 为协议头,则表示这是一个本地仓库。

若 uri 为 git 远程地址,则表示这是一个远程 git repository。

在 repository 的根目录下可以保存一个 manifes.yaml,用于保存 repository 的配置信息 repositoryConfig,包括名字、版本号(这个目前没有用到)、template 存放地址(默认在仓库的根目录下)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Repository represents a collection of runtimes, each containing templates.
type Repository struct {
// Name of the repository
// This can be for instance:
// directory name on FS or last part of git URL or arbitrary value defined by the Template author.
Name string
// Runtimes containing Templates loaded from the repo
Runtimes []Runtime
fs filesystem.Filesystem
uri string // URI which was used when initially creating
}

type repositoryConfig struct {
// DefaultName is the name indicated by the repository author.
// Stored in the yaml attribute "name", it is only consulted during initial
// addition of the repo as the default option.
DefaultName string `yaml:"name,omitempty"`
// Version of the repository.
Version string `yaml:"version,omitempty"`
// TemplatesPath defines an optional path within the repository at which
// templates are stored. By default this is the repository root.
TemplatesPath string `yaml:"templates,omitempty"`
}

Client 不直接操作 Repository,而是通过 Repository 管理器 Repositories 操作仓库。

Runtime

Runtime 表示不同的语言,在 Repository 中维护了 Runtime 切片,在 Runtime 中维护了 Template 切片。

1
2
3
4
5
6
7
8
9
// Runtime is a division of templates within a repository of templates for a
// given runtime (source language plus environmentally available services
// and libraries)
type Runtime struct {
// Name of the runtime
Name string
// Templates defined for the runtime
Templates []Template
}

在 Repository 中,读取 repositoryConfig.TempatesPath(默认为 Repository 根目录)下的所有子文件夹,每一个子文件夹的名称就是一个 Runtime:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// repositoryRuntimes returns runtimes defined in this repository's filesystem.
// The views are denormalized, using the parent repository's values
// for inherited fields BuildConfig and HealthEndpoints as the default values
// for the runtimes and templates. The runtimes and templates themselves can
// override these values by specifying new values in thir config files.
func repositoryRuntimes(fs filesystem.Filesystem, repoName string, repoConfig repositoryConfig) (runtimes []Runtime, err error) {
runtimes = []Runtime{}

// Load runtimes
fis, err := fs.ReadDir(repoConfig.TemplatesPath)
if err != nil {
return
}
for _, fi := range fis {
// ignore files and hidden dirs
if !fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
continue
}
// Runtime, defaulted to values inherited from the repository
runtime := Runtime{
Name: fi.Name(),
}

// Runtime Templates
// Load from repo filesystem for runtime. Will inherit values from the
// runtime such as BuildConfig, HealthEndpoints etc.
runtime.Templates, err = runtimeTemplates(fs, repoConfig.TemplatesPath, repoName, runtime.Name)
if err != nil {
return
}
runtimes = append(runtimes, runtime)
}
return
}

Template

Template 表示某个应用模版,在创建应用时,最终的操作就是将模版复制到指定的文件夹下。template 维护了一个 filesystem.Filesystem,表示以当前模版为根目录的文件系统。

在创建模版时,遍历 repositoryConfig.TemplatesPath/RuntimeName 下的每一个文件夹,文件夹的名字作为模版名字,使用 subFS 作为模版文件系统的实现

1
2
3
4
5
6
7
// Template, defaulted to values inherited from the runtime
t := template{
name: fi.Name(),
repository: repoName,
runtime: runtimeName,
fs: filesystem.NewSubFS(path.Join(runtimePath, fi.Name()), fs),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Template is a function project template.
// It can be used to instantiate new function project.
type Template interface {
// Name of this template.
Name() string
// Runtime for which this template applies.
Runtime() string
// Repository within which this template is contained. Value is set to the
// currently effective name of the repository, which may vary. It is user-
// defined when the repository is added, and can be set to "default" when
// the client is loaded in single repo mode. I.e. not canonical.
Repository() string
// Fullname is a calculated field of [repo]/[name] used
// to uniquely reference a template which may share a name
// with one in another repository.
Fullname() string
// Write updates fields of function f and writes project files to path pointed by f.Root.
Write(ctx context.Context, f *Dubbo) error
}

// template default implementation
type template struct {
name string
runtime string
repository string
fs filesystem.Filesystem
}

写入

在写入时,使用了 maskingFS,用于屏蔽当前模版下的 manifest 文件(虽然现在 manifest 没有什么用,并没有起到配置 template 的作用)。

1
2
3
4
5
6
7
8
func (t template) Write(ctx context.Context, f *Dubbo) error {
mask := func(p string) bool {
_, f := path.Split(p)
return f == templateManifest
}

return filesystem.CopyFromFS(".", f.Root, filesystem.NewMaskingFS(mask, t.fs)) // copy everything but manifest.yaml
}